Public/Add-DhRecordList.ps1
|
function Add-DhRecordList { <# .SYNOPSIS Add a list of records, one row each, that expand to show their details. .DESCRIPTION v2.3.0. For "one thing per row, with a lot to say about each": the hardware of every domain controller, the settings of every GPO, the state of every backup job. A row shows a status dot, a title, a sub line and up to four headline metrics; clicking it opens the body, which is made of sections: label/value tiles, small tables, bars, status lists, check lists, text and links (the same sections as the detail drawer and Add-DhKeyValue). A filter box searches every row (title, sub line, metrics and the text in the sections). Expand all / Collapse all are above the list. Printed, every record is shown expanded. All text is HTML-encoded (there is no -AllowHtml). .PARAMETER Report Dashboard object from New-DhDashboard. .PARAMETER Id Unique block identifier (alphanumeric, - or _). .PARAMETER Title Block heading. .PARAMETER Items Array of hashtables: @{ Title = 'vit00-dc01' # REQUIRED Subtitle = 'corp.example - FIT00' # optional Status = 'ok' # optional: ok | warn | danger | info | nodata Metrics = @(@{ Label='RAM'; Value='8 GB' }, @{ Label='CPU'; Value='2%'; Status='ok' }) # max 4 Sections = @( # optional; see ConvertTo-DhSections @{ Kind='kv'; Title='System'; Items=@(@{ Label='Model'; Value='VMware20,1' }) } @{ Kind='table'; Title='Volumes'; Columns=@('Drive','Free'); Rows=@(,@('C:','73 GB')) } ) Id = 'dc01' # optional stable id (default r1, r2, ...) } Section kinds: kv, bars, statusList, checks, table, text, links. .PARAMETER Filterable Text filter box (default $true). .PARAMETER OpenAll Start with every record expanded. .PARAMETER MaxItems Upper limit on the number of records (default 500). More is a warning, not an error: the page works but grows with every record. .EXAMPLE Add-DhRecordList -Report $r -Id 'dcs' -Title 'Hardware and performance' -NavPath 'Hardware' -Items @( @{ Title='vit00-dc01'; Subtitle='corp.example - FIT00'; Status='ok' Metrics=@(@{ Label='RAM'; Value='8 GB' }, @{ Label='Cores'; Value='4' }, @{ Label='CPU'; Value='2%' }) Sections=@( @{ Kind='kv'; Title='System'; Items=@(@{ Label='Manufacturer'; Value='VMware, Inc.' }) } @{ Kind='table'; Title='Logical volumes'; Columns=@('Drive','Size','Free'); Rows=@(,@('C:','99 GB','73 GB')) }) } ) #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $Report, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9_-]+$')] [string] $Id, [string] $Title = 'Records', [Parameter(Mandatory)] [object[]] $Items, [bool] $Filterable = $true, [switch] $OpenAll, [ValidateRange(1, 100000)] [int] $MaxItems = 500, [bool] $Collapsible = $true, [bool] $DefaultOpen = $true, [ValidateRange(0,12)] [int] $GridSpan = 0, [string] $NavGroup = '', [string] $NavSubGroup = '', # ---- v2.0.0 navigation (see readme-dev.md section 5) ---- [string] $NavPath = '', [ValidateSet('','Exact','Subtree','Global')] [string] $NavScope = '', [string] $NavLabel = '', [int] $NavOrder = 0 ) $navDecl = Resolve-DhNavDeclaration -NavPath $NavPath -NavScope $NavScope ` -NavGroup $NavGroup -NavSubGroup $NavSubGroup ` -ItemKind 'Block' -Context 'Add-DhRecordList' if (-not $Report.Contains('_DhNavSeq')) { $Report['_DhNavSeq'] = 0 } $navSeqNo = [int]$Report['_DhNavSeq']; $Report['_DhNavSeq'] = $navSeqNo + 1 if (-not $Report.Contains('Blocks')) { $Report['Blocks'] = [System.Collections.Generic.List[hashtable]]::new() } foreach ($existing in $Report.Blocks) { if ($existing.Id -eq $Id) { throw "Add-DhRecordList: A block with Id '$Id' already exists in this report. Use a unique Id." } } if (@($Items).Count -eq 0) { throw "Add-DhRecordList '$Id': -Items is empty." } if (@($Items).Count -gt $MaxItems) { Write-Warning "Add-DhRecordList '$Id': $(@($Items).Count) records exceeds -MaxItems ($MaxItems). Every record is built into the page, so it grows with each one; consider a table with -DetailDrawer instead." } $seenIds = @{} $n = 0 $norm = foreach ($it in $Items) { $n++ if ($it -isnot [System.Collections.IDictionary]) { throw "Add-DhRecordList '$Id': record #$n must be a hashtable. Got: $($it.GetType().Name)" } if (-not $it.Contains('Title') -or [string]::IsNullOrWhiteSpace([string]$it['Title'])) { throw "Add-DhRecordList '$Id': record #$n has no Title." } $ctx = "Add-DhRecordList '$Id' record '$($it['Title'])'" $rid = if ($it.Contains('Id') -and -not [string]::IsNullOrWhiteSpace([string]$it['Id'])) { [string]$it['Id'] } else { "r$n" } if ($rid -notmatch '^[A-Za-z0-9_-]+$') { throw "${ctx}: Id must be alphanumeric, - or _. Got: '$rid'" } if ($seenIds.ContainsKey($rid)) { throw "${ctx}: duplicate record Id '$rid'." } $seenIds[$rid] = $true $metrics = @(foreach ($m in @($(if ($it.Contains('Metrics')) { $it['Metrics'] } else { $null }))) { if ($null -eq $m) { continue } if ($m -isnot [System.Collections.IDictionary] -or -not $m.Contains('Label')) { throw "${ctx}: each Metrics entry must be @{ Label; Value; Status }." } [ordered]@{ Label = [string]$m['Label'] Value = if ($m.Contains('Value') -and $null -ne $m['Value']) { [string]$m['Value'] } else { '' } Status = ConvertTo-DhStatusName $(if ($m.Contains('Status')) { $m['Status'] } else { $null }) } }) if ($metrics.Count -gt 4) { throw "${ctx}: at most 4 Metrics are shown in the row. Got $($metrics.Count); put the rest in a kv section." } $sections = ConvertTo-DhSections -Context $ctx -Sections @($(if ($it.Contains('Sections')) { $it['Sections'] } else { $null })) [ordered]@{ Id = $rid Title = [string]$it['Title'] Subtitle = if ($it.Contains('Subtitle') -and $null -ne $it['Subtitle']) { [string]$it['Subtitle'] } else { '' } Status = ConvertTo-DhStatusName $(if ($it.Contains('Status')) { $it['Status'] } else { $null }) Metrics = $metrics Sections = $sections } } $Report.Blocks.Add([ordered]@{ BlockType = 'recordlist' Id = $Id Title = $Title Items = @($norm) Filterable = $Filterable OpenAll = [bool]$OpenAll NavGroup = $NavGroup NavSubGroup = $NavSubGroup NavPath = $NavPath NavSegments = $navDecl.Path NavSeqNo = $navSeqNo NavScope = $navDecl.Scope NavLabel = $NavLabel NavOrder = $NavOrder GridSpan = $GridSpan Collapsible = $Collapsible DefaultOpen = $DefaultOpen }) Write-Verbose "Add-DhRecordList: '$Id' ($(@($norm).Count) record(s))." } |