Skip to content

Commit 17ed2b3

Browse files
Merge pull request KelvinTegelaar#6343 from KelvinTegelaar/dev
Dev to hf
2 parents 6138cf5 + 777c194 commit 17ed2b3

9 files changed

Lines changed: 172 additions & 101 deletions

File tree

.github/copilot-instructions.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## Commit messages
2+
3+
Generate all commit messages in **Conventional Commits** format:
4+
5+
```
6+
<type>(<optional scope>): <description>
7+
8+
[optional body]
9+
10+
[optional footer(s)]
11+
```
12+
13+
**Rules:**
14+
15+
- **Type** is required and must be one of:
16+
- `feat` — a new feature
17+
- `fix` — a bug fix
18+
- `docs` — documentation only
19+
- `style` — formatting, whitespace, no code-behavior change
20+
- `refactor` — code change that neither fixes a bug nor adds a feature
21+
- `perf` — a performance improvement
22+
- `test` — adding or correcting tests
23+
- `build` — build system or dependency changes
24+
- `ci` — CI/CD configuration changes
25+
- `chore` — routine maintenance, no production code change
26+
- `revert` — reverts a previous commit
27+
- **Scope** is optional and given in parentheses after the type (e.g. `feat(identity):`). Use a short, lowercase area name when it adds clarity.
28+
- **Description** is a short, imperative-mood summary ("add", not "added"/"adds"), lowercase, no trailing period, ideally ≤ 72 characters.
29+
- **Body** (optional) explains the *what* and *why*, not the *how*. Separate it from the description with one blank line.
30+
- **Breaking changes** are indicated with a `!` before the colon (e.g. `feat!:`) and/or a `BREAKING CHANGE:` footer describing the change.
31+
- Reference issues/PRs in the footer where relevant (e.g. `Closes #123`).
32+
33+
**Examples:**
34+
35+
```
36+
feat(identity): add bulk user offboarding endpoint
37+
fix(graph): handle expired token on retry
38+
docs: update authentication model overview
39+
refactor(standards)!: rename remediation parameter
40+
```

.github/workflows/Conventional_Commits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
github-token: ${{ secrets.GITHUB_TOKEN }}
2525
script: |
2626
const title = context.payload.pull_request.title || '';
27-
const pattern = /^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?!?: .+/;
27+
const pattern = /^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?!?: .+/i;
2828
2929
if (pattern.test(title)) {
3030
console.log(`✓ PR title follows Conventional Commits format: "${title}"`);

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.6.0",
3+
"version": "10.6.1",
44
"author": "CIPP Contributors",
55
"homepage": "https://cipp.app/",
66
"bugs": {

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.6.0"
2+
"version": "10.6.1"
33
}

src/components/CippComponents/CippAppTemplateDrawer.jsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,14 +187,18 @@ export const CippAppTemplateDrawer = ({
187187
value: typeValue,
188188
}
189189
}
190-
// Normalize "Save as Template" configs (IntuneBody format) to form fields
191-
if (config.IntuneBody && !config.applicationName) {
190+
// Normalize "Save as Template" configs to form fields, canonicalizing on the
191+
// lowercase applicationName so deploy-time ConvertFrom-Json never sees both casings.
192+
if (config.IntuneBody) {
192193
const body = config.IntuneBody
193-
config.applicationName = config.ApplicationName || body.displayName || ''
194-
config.description = body.description || ''
195-
config.AssignTo = config.assignTo || 'On'
194+
if (!config.applicationName) {
195+
config.applicationName = config.ApplicationName || body.displayName || ''
196+
}
197+
delete config.ApplicationName
198+
if (!config.description) config.description = body.description || ''
199+
if (!config.AssignTo) config.AssignTo = config.assignTo || 'On'
196200
// WinGet/Store: packageIdentifier
197-
if (body.packageIdentifier) {
201+
if (!config.packagename && body.packageIdentifier) {
198202
config.packagename = body.packageIdentifier
199203
}
200204
// Chocolatey: extract package name from detection rules or install command
@@ -206,7 +210,7 @@ export const CippAppTemplateDrawer = ({
206210
if (match) config.packagename = match[1]
207211
}
208212
// Chocolatey: custom repo
209-
if (body.installCommandLine) {
213+
if (!config.customRepo && body.installCommandLine) {
210214
const repoMatch = body.installCommandLine.match(/-CustomRepo\s+(\S+)/i)
211215
if (repoMatch) config.customRepo = repoMatch[1]
212216
}

src/components/CippFormPages/CippAddEditUser.jsx

Lines changed: 112 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ const CippAddEditUser = (props) => {
2020
const [selectedTemplate, setSelectedTemplate] = useState(null)
2121
const [displayNameManuallySet, setDisplayNameManuallySet] = useState(false)
2222
const [usernameManuallySet, setUsernameManuallySet] = useState(false)
23+
// Tracks the template already applied to the form so we can tell a first
24+
// apply (fill empty fields) apart from a switch (replace/clear stale values)
25+
const appliedTemplateKeyRef = useRef(null)
2326
const router = useRouter()
2427
const { userId } = router.query
2528

@@ -261,6 +264,7 @@ const CippAddEditUser = (props) => {
261264
// Only clear selected template if it's not the default template
262265
if (selectedTemplate && !selectedTemplate.defaultForTenant) {
263266
setSelectedTemplate(null)
267+
appliedTemplateKeyRef.current = null
264268
}
265269
}
266270
}, [
@@ -290,100 +294,123 @@ const CippAddEditUser = (props) => {
290294

291295
// Auto-populate fields when template selected
292296
useEffect(() => {
293-
if (formType === 'add' && watchedFields.userTemplate?.addedFields) {
294-
const template = watchedFields.userTemplate.addedFields
295-
setSelectedTemplate(template)
297+
if (formType !== 'add' || !watchedFields.userTemplate?.addedFields) return
298+
const template = watchedFields.userTemplate.addedFields
299+
const templateKey = watchedFields.userTemplate.value ?? template.GUID ?? template.templateName
296300

297-
// Reset manual edit flags when template changes
298-
setDisplayNameManuallySet(false)
299-
setUsernameManuallySet(false)
301+
// Distinguish the first apply from a switch. On a switch we replace
302+
// template-driven fields (and clear ones the new template doesn't define)
303+
// so stale values from the previous template don't linger. On the first
304+
// apply we only fill fields that have a template value, so we don't clobber
305+
// input the user already entered or copied from another user.
306+
const isSwitch =
307+
appliedTemplateKeyRef.current !== null && appliedTemplateKeyRef.current !== templateKey
308+
appliedTemplateKeyRef.current = templateKey
300309

301-
// Only set fields if they don't already have values (don't override user input)
302-
const setFieldIfEmpty = (fieldName, value) => {
303-
if (value) {
304-
formControl.setValue(fieldName, value)
305-
}
306-
}
310+
setSelectedTemplate(template)
307311

308-
// Populate form fields from template
309-
if (template.primDomain) {
310-
// If primDomain is an object, use it as-is; if it's a string, convert to object
311-
const primDomainValue =
312-
typeof template.primDomain === 'string'
313-
? { label: template.primDomain, value: template.primDomain }
314-
: template.primDomain
315-
formControl.setValue('primDomain', primDomainValue)
316-
}
317-
if (template.usageLocation) {
318-
// Handle both object and string formats
319-
const usageLocationCode =
320-
typeof template.usageLocation === 'string'
321-
? template.usageLocation
322-
: template.usageLocation?.value
323-
const country = countryList.find((c) => c.Code === usageLocationCode)
324-
if (country) {
325-
setFieldIfEmpty('usageLocation', {
326-
label: country.Name,
327-
value: country.Code,
328-
})
329-
}
330-
}
331-
setFieldIfEmpty('jobTitle', template.jobTitle)
332-
setFieldIfEmpty('streetAddress', template.streetAddress)
333-
setFieldIfEmpty('city', template.city)
334-
setFieldIfEmpty('state', template.state)
335-
setFieldIfEmpty('postalCode', template.postalCode)
336-
setFieldIfEmpty('country', template.country)
337-
setFieldIfEmpty('companyName', template.companyName)
338-
setFieldIfEmpty('department', template.department)
339-
setFieldIfEmpty('mobilePhone', template.mobilePhone)
340-
const templateBusinessPhone = Array.isArray(template.businessPhones)
341-
? template.businessPhones[0]
342-
: template.businessPhones
343-
if (templateBusinessPhone) {
344-
formControl.setValue('businessPhones', [templateBusinessPhone])
345-
}
312+
// Reset manual edit flags when template changes
313+
setDisplayNameManuallySet(false)
314+
setUsernameManuallySet(false)
346315

347-
// Handle licenses - need to match the format expected by CippFormLicenseSelector
348-
if (template.licenses && Array.isArray(template.licenses)) {
349-
setFieldIfEmpty('licenses', template.licenses)
316+
// Apply a template value to a field. When the template has a value we set
317+
// it; when it doesn't and this is a switch we clear the field (emptyValue)
318+
// so the previous template's value doesn't linger.
319+
const applyField = (fieldName, value, emptyValue = '') => {
320+
const hasValue = Array.isArray(value)
321+
? value.length > 0
322+
: value !== undefined && value !== null && value !== ''
323+
if (hasValue) {
324+
formControl.setValue(fieldName, value, { shouldDirty: true })
325+
} else if (isSwitch) {
326+
formControl.setValue(fieldName, emptyValue, { shouldDirty: true })
350327
}
328+
}
351329

352-
// Handle groups from template
353-
const templateGroups = template.addToGroups || template.groupMemberships
354-
if (templateGroups) {
355-
const rawGroups = Array.isArray(templateGroups) ? templateGroups : [templateGroups]
356-
const groups = rawGroups.map((g) => {
357-
if (g.label && g.value) return g
358-
const groupType = g.groupTypes?.includes('Unified')
359-
? 'Microsoft 365'
360-
: g.mailEnabled && !g.groupTypes?.includes('Unified')
361-
? g.securityEnabled
362-
? 'Mail-Enabled Security'
363-
: 'Distribution list'
364-
: 'Security'
365-
return {
366-
label: g.displayName,
367-
value: g.id,
368-
addedFields: { groupType },
369-
}
370-
})
371-
if (groups.length > 0) {
372-
const currentGroups = watchedFields.AddToGroups
373-
if (!currentGroups || (Array.isArray(currentGroups) && currentGroups.length === 0)) {
374-
formControl.setValue('AddToGroups', groups, { shouldDirty: true })
375-
}
376-
}
330+
// Primary domain - accept both object and string formats
331+
const primDomainValue = template.primDomain
332+
? typeof template.primDomain === 'string'
333+
? { label: template.primDomain, value: template.primDomain }
334+
: template.primDomain
335+
: null
336+
applyField('primDomain', primDomainValue, null)
337+
338+
// Usage location - accept both object and string formats
339+
const usageLocationCode =
340+
typeof template.usageLocation === 'string'
341+
? template.usageLocation
342+
: template.usageLocation?.value
343+
const country = usageLocationCode
344+
? countryList.find((c) => c.Code === usageLocationCode)
345+
: null
346+
applyField(
347+
'usageLocation',
348+
country ? { label: country.Name, value: country.Code } : null,
349+
null
350+
)
351+
352+
applyField('jobTitle', template.jobTitle)
353+
applyField('streetAddress', template.streetAddress)
354+
applyField('city', template.city)
355+
applyField('state', template.state)
356+
applyField('postalCode', template.postalCode)
357+
applyField('country', template.country)
358+
applyField('companyName', template.companyName)
359+
applyField('department', template.department)
360+
applyField('mobilePhone', template.mobilePhone)
361+
362+
const templateBusinessPhone = Array.isArray(template.businessPhones)
363+
? template.businessPhones[0]
364+
: template.businessPhones
365+
applyField('businessPhones', templateBusinessPhone ? [templateBusinessPhone] : [], [])
366+
367+
// Licenses - match the format expected by CippFormLicenseSelector
368+
applyField(
369+
'licenses',
370+
Array.isArray(template.licenses) ? template.licenses : [],
371+
[]
372+
)
373+
374+
// Groups from template
375+
const templateGroups = template.addToGroups || template.groupMemberships
376+
const rawGroups = templateGroups
377+
? Array.isArray(templateGroups)
378+
? templateGroups
379+
: [templateGroups]
380+
: []
381+
const groups = rawGroups.map((g) => {
382+
if (g.label && g.value) return g
383+
const groupType = g.groupTypes?.includes('Unified')
384+
? 'Microsoft 365'
385+
: g.mailEnabled && !g.groupTypes?.includes('Unified')
386+
? g.securityEnabled
387+
? 'Mail-Enabled Security'
388+
: 'Distribution list'
389+
: 'Security'
390+
return {
391+
label: g.displayName,
392+
value: g.id,
393+
addedFields: { groupType },
377394
}
395+
})
396+
applyField('AddToGroups', groups, [])
378397

379-
// Populate custom user attributes from template
380-
if (template.defaultAttributes) {
381-
Object.entries(template.defaultAttributes).forEach(([key, attr]) => {
382-
if (attr?.Value) {
383-
setFieldIfEmpty(`defaultAttributes.${key}.Value`, attr.Value)
384-
}
398+
// Custom user attributes. On a switch, clear every known attribute field
399+
// first so attributes the new template doesn't define don't linger, then
400+
// apply the template's values.
401+
if (isSwitch) {
402+
userSettingsDefaults?.userAttributes
403+
?.filter((attribute) => attribute.value !== 'sponsor')
404+
.forEach((attribute) => {
405+
formControl.setValue(`defaultAttributes.${attribute.label}.Value`, '', {
406+
shouldDirty: true,
407+
})
385408
})
386-
}
409+
}
410+
if (template.defaultAttributes) {
411+
Object.entries(template.defaultAttributes).forEach(([key, attr]) => {
412+
applyField(`defaultAttributes.${key}.Value`, attr?.Value)
413+
})
387414
}
388415
}, [watchedFields.userTemplate, formType])
389416

src/data/standards.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6531,7 +6531,7 @@
65316531
"label": "Conditional Access Template",
65326532
"cat": "Templates",
65336533
"multiple": true,
6534-
"disabledFeatures": { "report": true, "warn": true, "remediate": false },
6534+
"disabledFeatures": { "report": false, "warn": false, "remediate": false },
65356535
"impact": "High Impact",
65366536
"addedDate": "2023-12-30",
65376537
"tag": [

src/layouts/config.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ export const nativeMenuItems = [
585585
},
586586
{
587587
title: 'Device Management',
588-
permissions: ['Endpoint.MEM.*'],
588+
permissions: ['Endpoint.MEM.*', 'Endpoint.Device.*'],
589589
items: [
590590
{
591591
title: 'Devices',
@@ -821,7 +821,7 @@ export const nativeMenuItems = [
821821
},
822822
{
823823
title: 'Transport',
824-
permissions: ['Exchange.TransportRule.*'],
824+
permissions: ['Exchange.TransportRule.*', 'Exchange.Connector.*'],
825825
items: [
826826
{
827827
title: 'Transport rules',
@@ -849,7 +849,7 @@ export const nativeMenuItems = [
849849
},
850850
{
851851
title: 'Spamfilter',
852-
permissions: ['Exchange.SpamFilter.*'],
852+
permissions: ['Exchange.SpamFilter.*', 'Exchange.ConnectionFilter.*'],
853853
items: [
854854
{
855855
title: 'Spamfilter',
@@ -882,7 +882,7 @@ export const nativeMenuItems = [
882882
},
883883
{
884884
title: 'Resource Management',
885-
permissions: ['Exchange.Equipment.*'],
885+
permissions: ['Exchange.Equipment.*', 'Exchange.Room.*'],
886886
items: [
887887
{
888888
title: 'Equipment',

src/pages/teams-share/sharing-report/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ const Page = () => {
248248
>
249249
<Typography variant="body2" color="text.secondary">
250250
{summary.lastDataRefresh
251-
? `Last data refresh: ${new Date(summary.lastDataRefresh).toLocaleString()}`
251+
? `Last data refresh: ${new Date(summary.lastDataRefresh.UtcDateTime).toLocaleString()}`
252252
: ''}
253253
</Typography>
254254
<Button

0 commit comments

Comments
 (0)