Skip to content

Commit 5b3f6ab

Browse files
authored
Merge pull request #17 from KelvinTegelaar/main
[pull] main from KelvinTegelaar:main
2 parents 5e928f2 + ff61271 commit 5b3f6ab

53 files changed

Lines changed: 3368 additions & 742 deletions

File tree

Some content is hidden

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

.github/workflows/pr_check.yml

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
# Only process fork PRs with specific branch conditions
2323
# Must be a fork AND (source is main/master OR target is main/master)
2424
if: |
25-
github.event.pull_request.head.repo.fork == true &&
25+
github.event.pull_request.head.repo.fork == true &&
2626
((github.event.pull_request.head.ref == 'main' || github.event.pull_request.head.ref == 'master') ||
2727
(github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master'))
2828
uses: actions/github-script@v9
@@ -31,7 +31,31 @@ jobs:
3131
script: |
3232
let message = '';
3333
34+
// Check if the fork has open PRs (indicates pull bot or similar is active)
35+
const forkOwner = context.payload.pull_request.head.repo.owner.login;
36+
const forkRepo = context.payload.pull_request.head.repo.name;
37+
const forkPullsUrl = context.payload.pull_request.head.repo.html_url + '/pulls';
38+
39+
let openPRs = [];
40+
try {
41+
const { data: prs } = await github.rest.pulls.list({
42+
owner: forkOwner,
43+
repo: forkRepo,
44+
state: 'open',
45+
per_page: 5
46+
});
47+
openPRs = prs;
48+
} catch (e) {
49+
// Can't read fork PRs — skip
50+
}
51+
3452
message += '🔄 If you are attempting to update your CIPP repo please follow the instructions at: https://docs.cipp.app/setup/self-hosting-guide/updating. Are you a sponsor? Contact the helpdesk for direct assistance with updating to the latest version.';
53+
54+
if (openPRs.length > 0) {
55+
message += ` It looks like you may already have a pending update PR on your fork — check your [open pull requests](${forkPullsUrl}) to accept it.`;
56+
} else {
57+
message += ` You can enable [Pull Bot](https://github.com/apps/pull) or [Repo Sync](https://github.com/apps/repo-sync) to automatically keep your fork up to date.`;
58+
}
3559
message += '\n\n';
3660
3761
// Check if PR is targeting main/master
@@ -40,7 +64,7 @@ jobs:
4064
}
4165
4266
// Check if PR is from a fork's main/master branch
43-
if (context.payload.pull_request.head.repo.fork &&
67+
if (context.payload.pull_request.head.repo.fork &&
4468
(context.payload.pull_request.head.ref === 'main' || context.payload.pull_request.head.ref === 'master')) {
4569
message += '⚠️ This PR cannot be merged because it originates from your fork\'s main/master branch. If you are attempting to contribute code please PR from your dev branch or another non-main/master branch.\n\n';
4670
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cipp",
3-
"version": "10.4.3",
3+
"version": "10.4.4",
44
"author": "CIPP Contributors",
55
"homepage": "https://cipp.app/",
66
"bugs": {

public/languageList.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,13 @@
160160
"languageTag": "Danish (da-DK)",
161161
"LCID": "1030"
162162
},
163+
{
164+
"language": "Dutch",
165+
"Geographic area": "Belgium",
166+
"tag": "nl-BE",
167+
"languageTag": "Dutch (nl-BE)",
168+
"LCID": "2067"
169+
},
163170
{
164171
"language": "Dutch",
165172
"Geographic area": "Netherlands",

public/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "10.4.3"
2+
"version": "10.4.4"
33
}

src/components/CippCards/CippDomainCards.jsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,13 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false })
470470
waiting: !!domain,
471471
});
472472

473+
const { data: autoDiscoverData, isFetching: autoDiscoverLoading } = ApiGetCall({
474+
url: "/api/ListDomainHealth",
475+
queryKey: `autodiscover-${domain}`,
476+
data: { Domain: domain, Action: "ReadAutoDiscover" },
477+
waiting: !!domain,
478+
});
479+
473480
const { data: httpsData, isFetching: httpsLoading } = ApiGetCall({
474481
url: "/api/ListDomainHealth",
475482
queryKey: `https-${domain}-${subdomains}`,
@@ -684,6 +691,26 @@ export const CippDomainCards = ({ domain: propDomain = "", fullwidth = false })
684691
}
685692
/>
686693
</Grid>
694+
<Grid size={{ md: gridItemSize, xs: 12 }}>
695+
<DomainResultCard
696+
title="AutoDiscover"
697+
data={autoDiscoverData}
698+
isFetching={autoDiscoverLoading}
699+
info={
700+
<div>
701+
<p>
702+
AutoDiscover ({autoDiscoverData?.RecordType || "None"}):
703+
</p>
704+
<CippCodeBlock code={autoDiscoverData?.Record || "No record found"} />
705+
<ResultList
706+
passes={autoDiscoverData?.ValidationPasses}
707+
warns={autoDiscoverData?.ValidationWarns}
708+
fails={autoDiscoverData?.ValidationFails}
709+
/>
710+
</div>
711+
}
712+
/>
713+
</Grid>
687714
{enableHttps && (
688715
<Grid size={{ md: gridItemSize, xs: 12 }}>
689716
<DomainResultCard

src/components/CippComponents/CippBulkUserDrawer.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@ export const CippBulkUserDrawer = ({
9494
const handleRemoveItem = (row) => {
9595
if (row === undefined) return false;
9696
const currentData = formControl.getValues("bulkUser") || [];
97-
const index = currentData.findIndex((item) => item === row);
97+
const rowKey = JSON.stringify(row);
98+
const index = currentData.findIndex((item) => JSON.stringify(item) === rowKey);
99+
if (index === -1) return false;
98100
const newData = [...currentData];
99101
newData.splice(index, 1);
100102
formControl.setValue("bulkUser", newData, { shouldValidate: true });
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
import React, { useEffect, useState } from "react";
2+
import { Button, Divider, Stack } from "@mui/material";
3+
import { Grid } from "@mui/system";
4+
import { useForm, useFormState, useWatch } from "react-hook-form";
5+
import { RocketLaunch } from "@mui/icons-material";
6+
import { CippOffCanvas } from "./CippOffCanvas";
7+
import CippFormComponent from "./CippFormComponent";
8+
import { CippFormTenantSelector } from "./CippFormTenantSelector";
9+
import { CippApiResults } from "./CippApiResults";
10+
import { ApiPostCall } from "../../api/ApiCall";
11+
12+
const MODE_CONFIG = {
13+
DlpCompliancePolicy: {
14+
title: "Deploy DLP Compliance Policy",
15+
buttonLabel: "Deploy DLP Policy",
16+
postUrl: "/api/AddDlpCompliancePolicy",
17+
listTemplatesUrl: "/api/ListDlpCompliancePolicyTemplates",
18+
templateQueryKey: "TemplateListDlpCompliancePolicy",
19+
relatedQueryKeys: ["ListDlpCompliancePolicy", "ListDlpCompliancePolicyTemplates"],
20+
placeholder: `{
21+
"Name": "Block Credit Card data",
22+
"Comment": "Blocks documents containing credit card numbers",
23+
"Mode": "Enable",
24+
"ExchangeLocation": "All",
25+
"SharePointLocation": "All",
26+
"OneDriveLocation": "All",
27+
"RuleParams": {
28+
"Name": "Block Credit Card data Rule",
29+
"ContentContainsSensitiveInformation": [{ "name": "Credit Card Number", "minCount": "1" }],
30+
"BlockAccess": true
31+
}
32+
}`,
33+
},
34+
RetentionCompliancePolicy: {
35+
title: "Deploy Retention Compliance Policy",
36+
buttonLabel: "Deploy Retention Policy",
37+
postUrl: "/api/AddRetentionCompliancePolicy",
38+
listTemplatesUrl: "/api/ListRetentionCompliancePolicyTemplates",
39+
templateQueryKey: "TemplateListRetentionCompliancePolicy",
40+
relatedQueryKeys: [
41+
"ListRetentionCompliancePolicy",
42+
"ListRetentionCompliancePolicyTemplates",
43+
],
44+
placeholder: `{
45+
"Name": "7-year Email Retention",
46+
"Comment": "Retain Exchange mail for 7 years",
47+
"ExchangeLocation": "All",
48+
"Enabled": true,
49+
"RuleParams": {
50+
"Name": "7-year Email Retention Rule",
51+
"RetentionDuration": 2555,
52+
"RetentionComplianceAction": "Keep",
53+
"ExpirationDateOption": "ModificationAgeInDays"
54+
}
55+
}`,
56+
},
57+
SensitivityLabel: {
58+
title: "Deploy Sensitivity Label",
59+
buttonLabel: "Deploy Sensitivity Label",
60+
postUrl: "/api/AddSensitivityLabel",
61+
listTemplatesUrl: "/api/ListSensitivityLabelTemplates",
62+
templateQueryKey: "TemplateListSensitivityLabel",
63+
relatedQueryKeys: ["ListSensitivityLabel", "ListSensitivityLabelTemplates"],
64+
placeholder: `{
65+
"Name": "Confidential",
66+
"DisplayName": "Confidential",
67+
"Tooltip": "Confidential data, do not share externally",
68+
"Comment": "Internal-only confidential classification",
69+
"ContentType": "File, Email",
70+
"EncryptionEnabled": true,
71+
"EncryptionProtectionType": "Template",
72+
"ContentMarkingHeaderEnabled": true,
73+
"ContentMarkingHeaderText": "Confidential - Internal Use Only",
74+
"PolicyParams": {
75+
"Name": "Confidential Label Policy",
76+
"ExchangeLocation": "All",
77+
"Settings": [
78+
["mandatory", "false"],
79+
["disablemandatoryinoutlook", "true"]
80+
]
81+
}
82+
}`,
83+
},
84+
SensitiveInfoType: {
85+
title: "Deploy Sensitive Information Type",
86+
buttonLabel: "Deploy SIT",
87+
postUrl: "/api/AddSensitiveInfoType",
88+
listTemplatesUrl: "/api/ListSensitiveInfoTypeTemplates",
89+
templateQueryKey: "TemplateListSensitiveInfoType",
90+
relatedQueryKeys: ["ListSensitiveInfoType", "ListSensitiveInfoTypeTemplates"],
91+
placeholder: `{
92+
"Name": "Custom Employee ID",
93+
"Description": "Internal Employee ID format EMP-NNNNN",
94+
"Pattern": "EMP-\\\\d{5}",
95+
"Confidence": "High",
96+
"Recommended": true
97+
}
98+
99+
// Or with a base64-encoded XML rule pack:
100+
// {
101+
// "Name": "Custom Rule Pack",
102+
// "FileDataBase64": "<BASE64 encoded XML rule pack>"
103+
// }`,
104+
},
105+
};
106+
107+
export const CippDeployCompliancePolicyDrawer = ({
108+
mode,
109+
buttonText,
110+
requiredPermissions = [],
111+
PermissionButton = Button,
112+
}) => {
113+
const config = MODE_CONFIG[mode];
114+
115+
const [drawerVisible, setDrawerVisible] = useState(false);
116+
117+
const formControl = useForm({
118+
mode: "onChange",
119+
defaultValues: {
120+
selectedTenants: [],
121+
TemplateList: null,
122+
PowerShellCommand: "",
123+
},
124+
});
125+
126+
const { isValid } = useFormState({ control: formControl.control });
127+
128+
const templateListVal = useWatch({ control: formControl.control, name: "TemplateList" });
129+
130+
useEffect(() => {
131+
if (templateListVal?.value) {
132+
formControl.setValue("PowerShellCommand", JSON.stringify(templateListVal.value));
133+
}
134+
}, [templateListVal, formControl]);
135+
136+
const deployPolicy = ApiPostCall({
137+
urlFromData: true,
138+
relatedQueryKeys: config?.relatedQueryKeys ?? [],
139+
});
140+
141+
useEffect(() => {
142+
if (deployPolicy.isSuccess) {
143+
formControl.reset({
144+
selectedTenants: [],
145+
TemplateList: null,
146+
PowerShellCommand: "",
147+
});
148+
}
149+
}, [deployPolicy.isSuccess, formControl]);
150+
151+
if (!config) {
152+
return null;
153+
}
154+
155+
const handleSubmit = () => {
156+
formControl.trigger();
157+
if (!isValid) return;
158+
deployPolicy.mutate({
159+
url: config.postUrl,
160+
data: formControl.getValues(),
161+
});
162+
};
163+
164+
const handleCloseDrawer = () => {
165+
setDrawerVisible(false);
166+
formControl.reset({
167+
selectedTenants: [],
168+
TemplateList: null,
169+
PowerShellCommand: "",
170+
});
171+
};
172+
173+
return (
174+
<>
175+
<PermissionButton
176+
requiredPermissions={requiredPermissions}
177+
onClick={() => setDrawerVisible(true)}
178+
startIcon={<RocketLaunch />}
179+
>
180+
{buttonText ?? config.buttonLabel}
181+
</PermissionButton>
182+
<CippOffCanvas
183+
title={config.title}
184+
visible={drawerVisible}
185+
onClose={handleCloseDrawer}
186+
size="lg"
187+
footer={
188+
<Stack direction="row" spacing={2} justifyContent="flex-start">
189+
<Button
190+
variant="contained"
191+
color="primary"
192+
onClick={handleSubmit}
193+
disabled={deployPolicy.isLoading || !isValid}
194+
>
195+
{deployPolicy.isLoading
196+
? "Deploying..."
197+
: deployPolicy.isSuccess
198+
? "Deploy Another"
199+
: "Deploy"}
200+
</Button>
201+
<Button variant="outlined" onClick={handleCloseDrawer}>
202+
Close
203+
</Button>
204+
</Stack>
205+
}
206+
>
207+
<Grid container spacing={2} sx={{ mb: 2 }}>
208+
<Grid size={{ xs: 12 }}>
209+
<CippFormTenantSelector
210+
label="Select Tenants"
211+
formControl={formControl}
212+
name="selectedTenants"
213+
type="multiple"
214+
allTenants={true}
215+
preselectedEnabled={true}
216+
validators={{ required: "At least one tenant must be selected" }}
217+
/>
218+
</Grid>
219+
220+
<Divider sx={{ my: 1, width: "100%" }} />
221+
222+
<Grid size={{ xs: 12 }}>
223+
<CippFormComponent
224+
type="autoComplete"
225+
label="Select a template (optional)"
226+
name="TemplateList"
227+
formControl={formControl}
228+
multiple={false}
229+
api={{
230+
queryKey: config.templateQueryKey,
231+
labelField: "name",
232+
valueField: (option) => option,
233+
url: config.listTemplatesUrl,
234+
}}
235+
placeholder="Select a template or enter PowerShell JSON manually"
236+
/>
237+
</Grid>
238+
239+
<Divider sx={{ my: 1, width: "100%" }} />
240+
241+
<Grid size={{ xs: 12 }}>
242+
<CippFormComponent
243+
type="textField"
244+
label="Parameters (JSON)"
245+
name="PowerShellCommand"
246+
formControl={formControl}
247+
multiline
248+
rows={12}
249+
validators={{ required: "Please enter the PowerShell parameters as JSON." }}
250+
placeholder={config.placeholder}
251+
/>
252+
</Grid>
253+
254+
<CippApiResults apiObject={deployPolicy} />
255+
</Grid>
256+
</CippOffCanvas>
257+
</>
258+
);
259+
};

0 commit comments

Comments
 (0)