diff --git a/Tests/Shapes/ListTests.json b/Tests/Shapes/ListTests.json
index 7dd16b29e91d..a4553bd14c70 100644
--- a/Tests/Shapes/ListTests.json
+++ b/Tests/Shapes/ListTests.json
@@ -103,23 +103,19 @@
"ExoAcceptedDomains": "number",
"ExoAdminAuditLogConfig": "number",
"ExoAntiPhishPolicies": "number",
- "ExoAntiPhishPolicy": "number",
"ExoAntiPhishRules": "number",
"ExoAtpPolicyForO365": "number",
"ExoDkimSigningConfig": "number",
"ExoHostedContentFilterPolicy": "number",
"ExoHostedOutboundSpamFilterPolicy": "number",
"ExoMalwareFilterPolicies": "number",
- "ExoMalwareFilterPolicy": "number",
"ExoMalwareFilterRules": "number",
"ExoOrganizationConfig": "number",
"ExoQuarantinePolicy": "number",
"ExoRemoteDomain": "number",
"ExoSafeAttachmentPolicies": "number",
- "ExoSafeAttachmentPolicy": "number",
"ExoSafeAttachmentRules": "number",
"ExoSafeLinksPolicies": "number",
- "ExoSafeLinksPolicy": "number",
"ExoSafeLinksRules": "number",
"ExoSharingPolicy": "number",
"ExoTenantAllowBlockList": "number",
diff --git a/src/components/CippComponents/CippTranslations.jsx b/src/components/CippComponents/CippTranslations.jsx
index af96b19cd027..c4b337ded761 100644
--- a/src/components/CippComponents/CippTranslations.jsx
+++ b/src/components/CippComponents/CippTranslations.jsx
@@ -27,17 +27,17 @@ export const CippTranslations = {
ApplicationID: "Application ID",
ApplicationSecret: "Application Secret",
GUID: "GUID",
- portal_m365: "M365 Portal",
- portal_exchange: "Exchange Portal",
- portal_entra: "Entra Portal",
- portal_teams: "Teams Portal",
- portal_azure: "Azure Portal",
- portal_intune: "Intune Portal",
- portal_security: "Security Portal",
- portal_compliance: "Compliance Portal",
- portal_sharepoint: "SharePoint Portal",
- portal_platform: "Power Platform Portal",
- portal_bi: "Power BI Portal",
+ portal_m365: "M365",
+ portal_exchange: "Exchange",
+ portal_entra: "Entra",
+ portal_teams: "Teams",
+ portal_azure: "Azure",
+ portal_intune: "Intune",
+ portal_security: "Security",
+ portal_compliance: "Compliance",
+ portal_sharepoint: "SharePoint",
+ portal_platform: "Power Platform",
+ portal_bi: "Power BI",
"@odata.type": "Type",
roleDefinitionId: "GDAP Role",
FromIP: "From IP",
diff --git a/src/components/CippTable/CippDataTable.js b/src/components/CippTable/CippDataTable.js
index 100837ab17d3..0fc4f87432b7 100644
--- a/src/components/CippTable/CippDataTable.js
+++ b/src/components/CippTable/CippDataTable.js
@@ -856,6 +856,8 @@ export const CippDataTable = (props) => {
layoutMode: 'grid-no-grow',
enableRowVirtualization: true,
enableColumnVirtualization: true,
+ enableColumnResizing: true,
+ columnResizeMode: 'onChange',
rowVirtualizerOptions: {
overscan: 5,
},
diff --git a/src/components/CippTable/util-columnsFromAPI.js b/src/components/CippTable/util-columnsFromAPI.js
index b3d3c0e61be5..fa6259e7982b 100644
--- a/src/components/CippTable/util-columnsFromAPI.js
+++ b/src/components/CippTable/util-columnsFromAPI.js
@@ -9,58 +9,56 @@ const skipRecursion = ['location', 'ScheduledBackupValues', 'Tenant']
const MAX_SIZE_SAMPLE = 30
// Average character width in pixels at compact density (roughly 7–8px per char).
const CHAR_WIDTH = 8
-// Extra padding per cell (sort icon, filter icon, cell padding).
-const CELL_PADDING = 36
+// Extra padding per cell (sort icon, filter icon, cell padding, resize handle).
+const CELL_PADDING = 5
const MIN_COL_SIZE = 80
const MAX_COL_SIZE = 500
+// Extra pixels reserved in the header for MRT chrome (sort icon, column actions menu,
+// resize handle, filter icon). These sit alongside the header text and consume space.
+const HEADER_CHROME_PX = 75
+
// Measure the pixel width a column needs based on its header and sampled cell values.
// rawValues are the original data values (before formatting) — if they contain arrays or
// complex objects the column renders as a button/chip list, so we cap to header width.
-// Returns { size, minSize } where minSize is always header-width + 30px safe space.
+// Returns { size, minSize } where minSize is always header-width + chrome safe space.
const measureColumnSize = (header, valuesForColumn, rawValues) => {
const headerLen = header ? header.length : 6
- const headerPx = Math.round(headerLen * CHAR_WIDTH + CELL_PADDING + 30)
+ const headerPx = Math.round(headerLen * CHAR_WIDTH + CELL_PADDING + HEADER_CHROME_PX)
const minSize = Math.max(MIN_COL_SIZE, headerPx)
- // If any raw value is an array or complex object, the cell renders as a compact
- // button or chip list. We measure the longest individual chip/item rather than the
- // full joined text. For object arrays that format into comma-separated chip labels
- // (e.g. assignedLicenses), we split the formatted text on commas to measure each chip.
+ // If any raw value is an array or complex object, the cell renders as either:
+ // - A CippDataTableButton ("X items" button) for object arrays and plain objects
+ // - A CollapsibleChipList for string/primitive arrays
+ // Size accordingly: buttons are compact, chips need per-item measurement.
if (rawValues && rawValues.length > 0) {
const hasComplex = rawValues.some(
(v) => Array.isArray(v) || (typeof v === 'object' && v !== null)
)
if (hasComplex) {
+ // Check if these are object arrays or plain objects — they render as a small
+ // "X items" button (CippDataTableButton), so size to the button width.
+ const allObjectLike = rawValues.every((v) => {
+ if (v === null || v === undefined) return true // nulls are fine, they show "No items"
+ if (Array.isArray(v)) return v.some((el) => typeof el === 'object' && el !== null)
+ return typeof v === 'object'
+ })
+ if (allObjectLike) {
+ // "X items" button is roughly 80-100px wide — just use header width
+ return { size: minSize, minSize }
+ }
+
+ // String/primitive arrays → rendered as chip list. Measure longest chip,
+ // but cap per-chip text since chips truncate long values (e.g. email addresses).
+ const MAX_CHIP_TEXT = 15
let longestItem = headerLen
for (let i = 0; i < rawValues.length; i++) {
const v = rawValues[i]
if (Array.isArray(v)) {
- const isObjectArray = v.some((el) => typeof el === 'object' && el !== null)
- if (isObjectArray) {
- // Object arrays get translated by getCippFormatting into comma-joined text.
- // Split on ", " and measure each segment (each becomes a chip).
- const formatted = valuesForColumn[i]
- if (typeof formatted === 'string') {
- for (const seg of formatted.split(', ')) {
- if (seg.length > longestItem) longestItem = seg.length
- }
- }
- continue
- }
- // Arrays of strings/numbers → chips — measure each item
for (const el of v) {
const s = typeof el === 'string' ? el : el != null ? String(el) : ''
- if (s.length > longestItem) longestItem = s.length
- }
- }
- // Plain objects → may also format into chip text
- if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
- const formatted = valuesForColumn[i]
- if (typeof formatted === 'string') {
- for (const seg of formatted.split(', ')) {
- if (seg.length > longestItem) longestItem = seg.length
- }
+ const len = Math.min(s.length, MAX_CHIP_TEXT)
+ if (len > longestItem) longestItem = len
}
}
}
@@ -70,17 +68,27 @@ const measureColumnSize = (header, valuesForColumn, rawValues) => {
}
}
- let maxLen = headerLen
const sample =
valuesForColumn.length > MAX_SIZE_SAMPLE
? valuesForColumn.slice(0, MAX_SIZE_SAMPLE)
: valuesForColumn
- for (const v of sample) {
- const str = typeof v === 'string' ? v : v != null ? String(v) : ''
- // URLs render as icons/links in the cell — don't measure the full URL text.
- if (str.match(/^https?:\/\//i) || str.match(/^\/api\//i)) continue
- if (str.length > maxLen) maxLen = str.length
+ const lengths = sample
+ .map((v) => {
+ const str = typeof v === 'string' ? v : v != null ? String(v) : ''
+ // URLs render as icons/links in the cell — don't measure the full URL text.
+ if (str.match(/^https?:\/\//i) || str.match(/^\/api\//i)) return 0
+ return str.length
+ })
+ .sort((a, b) => a - b)
+
+ // Trim the top and bottom 10% to remove outliers, then use the longest remaining value.
+ let trimmedLengths = lengths
+ if (lengths.length >= 5) {
+ const trimCount = Math.max(1, Math.floor(lengths.length * 0.1))
+ trimmedLengths = lengths.slice(trimCount, lengths.length - trimCount)
}
+ const maxLen = Math.max(headerLen, ...trimmedLengths)
+
const px = Math.round(maxLen * CHAR_WIDTH + CELL_PADDING)
const size = Math.max(minSize, Math.min(MAX_COL_SIZE, px))
return { size, minSize }
@@ -210,7 +218,7 @@ export const utilColumnsFromAPI = (dataArray) => {
// Allow per-column size overrides for columns whose rendered output
// doesn't match text width (icons, progress bars, etc.).
- const sizeOverride = getCippColumnSize(accessorKey)
+ const sizeOverride = getCippColumnSize(accessorKey, header)
let finalSize = { ...measuredSize }
if (sizeOverride) {
const resolve = (v) => (v === 'header' ? measuredSize.minSize : v)
diff --git a/src/data/CIPPDBCacheTypes.json b/src/data/CIPPDBCacheTypes.json
index 28fac3a6fde8..8ff123ff4aff 100644
--- a/src/data/CIPPDBCacheTypes.json
+++ b/src/data/CIPPDBCacheTypes.json
@@ -204,26 +204,6 @@
"friendlyName": "Exchange Hosted Outbound Spam Filter Policy",
"description": "Exchange Online hosted outbound spam filter policy"
},
- {
- "type": "ExoAntiPhishPolicy",
- "friendlyName": "Exchange Anti-Phish Policy",
- "description": "Exchange Online anti-phishing policy"
- },
- {
- "type": "ExoSafeLinksPolicy",
- "friendlyName": "Exchange Safe Links Policy",
- "description": "Exchange Online Safe Links policy"
- },
- {
- "type": "ExoSafeAttachmentPolicy",
- "friendlyName": "Exchange Safe Attachment Policy",
- "description": "Exchange Online Safe Attachment policy"
- },
- {
- "type": "ExoMalwareFilterPolicy",
- "friendlyName": "Exchange Malware Filter Policy",
- "description": "Exchange Online malware filter policy"
- },
{
"type": "ExoAtpPolicyForO365",
"friendlyName": "Exchange ATP Policy for O365",
diff --git a/src/data/portals.json b/src/data/portals.json
index 5c8011ebff77..3d9cdb73b85a 100644
--- a/src/data/portals.json
+++ b/src/data/portals.json
@@ -1,6 +1,6 @@
[
{
- "label": "M365 Portal",
+ "label": "M365",
"name": "M365_Portal",
"url": "https://admin.cloud.microsoft/?delegatedOrg=initialDomainName",
"variable": "initialDomainName",
@@ -9,7 +9,7 @@
"icon": "GlobeAltIcon"
},
{
- "label": "Exchange Portal",
+ "label": "Exchange",
"name": "Exchange_Portal",
"url": "https://admin.cloud.microsoft/exchange?landingpage=homepage&form=mac_sidebar&delegatedOrg=initialDomainName#",
"variable": "initialDomainName",
@@ -18,7 +18,7 @@
"icon": "Mail"
},
{
- "label": "Entra Portal",
+ "label": "Entra",
"name": "Entra_Portal",
"url": "https://entra.microsoft.com/defaultDomainName",
"variable": "defaultDomainName",
@@ -27,7 +27,7 @@
"icon": "UsersIcon"
},
{
- "label": "Teams Portal",
+ "label": "Teams",
"name": "Teams_Portal",
"url": "https://admin.teams.microsoft.com/?delegatedOrg=initialDomainName",
"variable": "initialDomainName",
@@ -36,7 +36,7 @@
"icon": "FilePresent"
},
{
- "label": "Azure Portal",
+ "label": "Azure",
"name": "Azure_Portal",
"url": "https://portal.azure.com/defaultDomainName",
"variable": "defaultDomainName",
@@ -45,7 +45,7 @@
"icon": "ServerIcon"
},
{
- "label": "Intune Portal",
+ "label": "Intune",
"name": "Intune_Portal",
"url": "https://intune.microsoft.com/defaultDomainName",
"variable": "defaultDomainName",
@@ -54,7 +54,7 @@
"icon": "Laptop"
},
{
- "label": "SharePoint Admin",
+ "label": "SharePoint",
"name": "SharePoint_Admin",
"url": "/api/ListSharePointAdminUrl?tenantFilter=defaultDomainName",
"variable": "defaultDomainName",
@@ -63,7 +63,7 @@
"icon": "Share"
},
{
- "label": "Security Portal",
+ "label": "Security",
"name": "Security_Portal",
"url": "https://security.microsoft.com/?tid=customerId",
"variable": "customerId",
@@ -72,7 +72,7 @@
"icon": "Shield"
},
{
- "label": "Compliance Portal",
+ "label": "Compliance",
"name": "Compliance_Portal",
"url": "https://purview.microsoft.com/?tid=customerId",
"variable": "customerId",
@@ -81,7 +81,7 @@
"icon": "ShieldMoon"
},
{
- "label": "Power Platform Portal",
+ "label": "Power Platform",
"name": "Power_Platform_Portal",
"url": "https://admin.powerplatform.microsoft.com/account/login/customerId",
"variable": "customerId",
@@ -90,7 +90,7 @@
"icon": "PrecisionManufacturing"
},
{
- "label": "Power BI Portal",
+ "label": "Power BI",
"name": "Power_BI_Portal",
"url": "https://app.powerbi.com/admin-portal?ctid=customerId",
"variable": "customerId",
@@ -98,4 +98,4 @@
"external": true,
"icon": "BarChart"
}
-]
\ No newline at end of file
+]
diff --git a/src/pages/cipp/preferences.js b/src/pages/cipp/preferences.js
index f6bbde2e7704..2b5303fa8225 100644
--- a/src/pages/cipp/preferences.js
+++ b/src/pages/cipp/preferences.js
@@ -183,47 +183,47 @@ const Page = () => {
const portalLinksConfig = [
{
name: "portalLinks.M365_Portal",
- label: "M365 Portal",
+ label: "M365",
},
{
name: "portalLinks.Exchange_Portal",
- label: "Exchange Portal",
+ label: "Exchange",
},
{
name: "portalLinks.Entra_Portal",
- label: "Entra Portal",
+ label: "Entra",
},
{
name: "portalLinks.Teams_Portal",
- label: "Teams Portal",
+ label: "Teams",
},
{
name: "portalLinks.Azure_Portal",
- label: "Azure Portal",
+ label: "Azure",
},
{
name: "portalLinks.Intune_Portal",
- label: "Intune Portal",
+ label: "Intune",
},
{
name: "portalLinks.SharePoint_Admin",
- label: "SharePoint Admin",
+ label: "SharePoint",
},
{
name: "portalLinks.Security_Portal",
- label: "Security Portal",
+ label: "Security",
},
{
name: "portalLinks.Compliance_Portal",
- label: "Compliance Portal",
+ label: "Compliance",
},
{
name: "portalLinks.Power_Platform_Portal",
- label: "Power Platform Portal",
+ label: "Power Platform",
},
{
name: "portalLinks.Power_BI_Portal",
- label: "Power BI Portal",
+ label: "Power BI",
},
];
diff --git a/src/pages/tools/custom-tests/add.jsx b/src/pages/tools/custom-tests/add.jsx
index fb4387bbbdb8..6951fe57c507 100644
--- a/src/pages/tools/custom-tests/add.jsx
+++ b/src/pages/tools/custom-tests/add.jsx
@@ -406,7 +406,7 @@ All UPNs: {{join(Result[*].UserPrincipalName, ", ")}}`,
placeholder: `# Example: Find disabled users with licenses
param($TenantFilter, $DaysThreshold = 30)
-$users = New-CIPPDbRequest -TenantFilter $TenantFilter -Type 'Users'
+$users = Get-CIPPTestData -TenantFilter $TenantFilter -Type 'Users'
$results = $users | Where-Object {
$_.assignedLicenses.Count -gt 0 -and
$_.accountEnabled -eq $false
@@ -655,7 +655,7 @@ return $results`,
Data Access
- Read-only via New-CIPPDbRequest and Get-CIPPDbItem{' '}
+ Read-only via Get-CIPPTestData and Get-CIPPDbItem{' '}
with a -Type parameter.