Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions Tests/Shapes/ListTests.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
22 changes: 11 additions & 11 deletions src/components/CippComponents/CippTranslations.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions src/components/CippTable/CippDataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -856,6 +856,8 @@ export const CippDataTable = (props) => {
layoutMode: 'grid-no-grow',
enableRowVirtualization: true,
enableColumnVirtualization: true,
enableColumnResizing: true,
columnResizeMode: 'onChange',
rowVirtualizerOptions: {
overscan: 5,
},
Expand Down
84 changes: 46 additions & 38 deletions src/components/CippTable/util-columnsFromAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
Expand All @@ -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 }
Expand Down Expand Up @@ -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)
Expand Down
20 changes: 0 additions & 20 deletions src/data/CIPPDBCacheTypes.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
24 changes: 12 additions & 12 deletions src/data/portals.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
"label": "M365 Portal",
"label": "M365",
"name": "M365_Portal",
"url": "https://admin.cloud.microsoft/?delegatedOrg=initialDomainName",
"variable": "initialDomainName",
Expand All @@ -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",
Expand All @@ -18,7 +18,7 @@
"icon": "Mail"
},
{
"label": "Entra Portal",
"label": "Entra",
"name": "Entra_Portal",
"url": "https://entra.microsoft.com/defaultDomainName",
"variable": "defaultDomainName",
Expand All @@ -27,7 +27,7 @@
"icon": "UsersIcon"
},
{
"label": "Teams Portal",
"label": "Teams",
"name": "Teams_Portal",
"url": "https://admin.teams.microsoft.com/?delegatedOrg=initialDomainName",
"variable": "initialDomainName",
Expand All @@ -36,7 +36,7 @@
"icon": "FilePresent"
},
{
"label": "Azure Portal",
"label": "Azure",
"name": "Azure_Portal",
"url": "https://portal.azure.com/defaultDomainName",
"variable": "defaultDomainName",
Expand All @@ -45,7 +45,7 @@
"icon": "ServerIcon"
},
{
"label": "Intune Portal",
"label": "Intune",
"name": "Intune_Portal",
"url": "https://intune.microsoft.com/defaultDomainName",
"variable": "defaultDomainName",
Expand All @@ -54,7 +54,7 @@
"icon": "Laptop"
},
{
"label": "SharePoint Admin",
"label": "SharePoint",
"name": "SharePoint_Admin",
"url": "/api/ListSharePointAdminUrl?tenantFilter=defaultDomainName",
"variable": "defaultDomainName",
Expand All @@ -63,7 +63,7 @@
"icon": "Share"
},
{
"label": "Security Portal",
"label": "Security",
"name": "Security_Portal",
"url": "https://security.microsoft.com/?tid=customerId",
"variable": "customerId",
Expand All @@ -72,7 +72,7 @@
"icon": "Shield"
},
{
"label": "Compliance Portal",
"label": "Compliance",
"name": "Compliance_Portal",
"url": "https://purview.microsoft.com/?tid=customerId",
"variable": "customerId",
Expand All @@ -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",
Expand All @@ -90,12 +90,12 @@
"icon": "PrecisionManufacturing"
},
{
"label": "Power BI Portal",
"label": "Power BI",
"name": "Power_BI_Portal",
"url": "https://app.powerbi.com/admin-portal?ctid=customerId",
"variable": "customerId",
"target": "_blank",
"external": true,
"icon": "BarChart"
}
]
]
22 changes: 11 additions & 11 deletions src/pages/cipp/preferences.js
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
];

Expand Down
Loading