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.