Public/Add-DhTabs.ps1
|
function Add-DhTabs { <# .SYNOPSIS Add an inline tabbed content block (v1.5+). .DESCRIPTION Renders a tab strip with one content pane per tab. Distinct from the page-level NavGroup mechanism: NavGroup partitions the whole dashboard into named panels; Add-DhTabs creates a tabbed pivot inside a single block of the dashboard flow. Use it for "summary / details / settings" style pivots — when several related views share the same surface area and the user toggles between them locally without navigating away from the current panel. Each tab is a hashtable carrying: - Title (required) — the label shown on the tab strip - Content — free-form HTML rendered inside the tab pane, OR - HtmlBlockId — an Id of an Add-DhHtmlBlock previously declared Free-form Content is the simplest path. To embed a richer block (sparkline table, status grid, etc.) inside a tab, the easiest pattern is still to wrap the block in -NavGroup / -NavSubGroup and use the page-level nav; Add-DhTabs is intentionally limited to inline HTML. SECURITY: per-tab Content is injected into the DOM via innerHTML. Never pass untrusted external data (AD attributes, ticket titles, log lines, anything supplied by an actor outside your trust boundary) into Content without HTML-encoding it first with [System.Web.HttpUtility]::HtmlEncode(). Same policy as Add-DhHtmlBlock, Add-DhAlertBanner -Message, and Add-DhCollapsible -Content. .PARAMETER Report Dashboard object from New-DhDashboard. .PARAMETER Id Unique identifier (alphanumeric, dash, underscore). .PARAMETER Title Optional block heading shown above the tab strip. .PARAMETER Tabs Array of tab definition hashtables: @{ Title = 'Overview' # REQUIRED — tab label Content = '<p>...</p>' # REQUIRED — inline HTML Icon = 'X' # optional — glyph shown before the label Active = $true # optional — selects this tab on load (first by default) } .PARAMETER NavGroup Primary nav group label (enables two-tier nav). .PARAMETER NavSubGroup Optional second-level group under NavGroup (enables three-tier nav). .EXAMPLE Add-DhTabs -Report $report -Id 'auth-pivot' -Title 'Authentication detail' ` -Tabs @( @{ Title='Overview'; Content='<p>Auth subsystem is operating within SLA.</p>' } @{ Title='Errors'; Content='<p>3 auth errors in the last hour. <a href="#">View log</a>.</p>' } @{ Title='Configuration'; Content='<pre>kerberos.maxRetries=3 ntlm.fallback=disabled</pre>' } ) #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Collections.Specialized.OrderedDictionary] $Report, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9_-]+$')] [string] $Id, [string] $Title = '', [Parameter(Mandatory)] [object[]] $Tabs, [bool] $Collapsible = $true, # wrap the block in a collapsible header (chevron + click-to-toggle) [bool] $DefaultOpen = $true, # whether the collapsible body starts expanded (only used when -Collapsible is $true) [string] $NavGroup = '', [string] $NavSubGroup = '' ) 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-DhTabs: A block with Id '$Id' already exists in this report. Use a unique Id." } } if ($Tabs.Count -lt 1) { throw "Add-DhTabs: -Tabs must contain at least one tab." } $normTabs = foreach ($t in $Tabs) { if ($t -isnot [hashtable] -and $t -isnot [System.Collections.Specialized.OrderedDictionary]) { throw "Add-DhTabs: each tab must be a hashtable. Got: $($t.GetType().Name)" } foreach ($req in 'Title','Content') { if (-not $t.Contains($req) -or [string]::IsNullOrWhiteSpace([string]$t[$req])) { throw "Add-DhTabs: each tab must have a non-empty '$req' key." } } @{ Title = [string]$t['Title'] Content = [string]$t['Content'] Icon = if ($t.Contains('Icon') -and $null -ne $t['Icon']) { [string]$t['Icon'] } else { '' } Active = if ($t.Contains('Active') -and $null -ne $t['Active']) { [bool]$t['Active'] } else { $false } } } # If no tab is explicitly Active, mark the first one $explicit = @($normTabs | Where-Object { $_.Active }).Count if ($explicit -eq 0) { $normTabs[0].Active = $true } elseif ($explicit -gt 1) { # Keep only the first explicitly-active tab; reset the rest $seen = $false foreach ($t in $normTabs) { if ($t.Active -and -not $seen) { $seen = $true } else { $t.Active = $false } } } $Report.Blocks.Add([ordered]@{ BlockType = 'tabs' Id = $Id Title = $Title Tabs = @($normTabs) NavGroup = $NavGroup NavSubGroup = $NavSubGroup Collapsible = $Collapsible DefaultOpen = $DefaultOpen }) Write-Verbose "Add-DhTabs: '$Id' ($($normTabs.Count) tab(s))." } |