Private/Show-AACAssessmentView.ps1
|
function Show-AACAssessmentView { <# .SYNOPSIS Renders the end of an Invoke-AACAssessment run as a Spectre.Console view: what was found, what needs attention, and where the reports are. .DESCRIPTION The scope; tiles (subscriptions, resource groups, resources, types, Advisor High, retirements, Defender High, outages, unattached); the inventory by category (resources, sheets, the commonest types); the most common resource types as a bar chart; what needs attention (the next retirements, High-impact Advisor and Defender recommendations, recent outages, sheets that couldn't be read); and the files written, with how long each phase took. Output goes straight to the Spectre console; wrap the call in Invoke-AACPagedOutput to page it. #> [CmdletBinding()] param( [Parameter(Mandatory)] [hashtable] $Assessment, [System.Collections.IDictionary] $Scope, [object[]] $File = @(), [System.Collections.IDictionary] $Timing ) $escape = { param($Text) [Spectre.Console.Markup]::Escape([string]$Text) } $glyph = Get-AACGlyph $stats = $Assessment.Stats $find = { param([string] $Name) @($Assessment.Sheets | Where-Object { $_.Sheet -eq $Name }) | Select-Object -First 1 } $facts = [System.Collections.Generic.List[string]]::new() if ($script:AACSession) { $facts.Add("[white]$(& $escape $script:AACSession.Account)[/]") } if ($Scope) { foreach ($key in $Scope.Keys) { $facts.Add("$(& $escape $key): $(& $escape $Scope[$key])") } } $facts.Add((Get-Date).ToString('d MMM yyyy HH:mm')) Write-AACMarkup "[grey58]$($facts -join " $($glyph.Dot) ")[/]" [Spectre.Console.AnsiConsole]::WriteLine() Show-AACTileRow -Tile @( @{ Value = '{0:N0}' -f $stats.Subscriptions; Caption = 'subscriptions'; Color = 'deepskyblue1' } @{ Value = '{0:N0}' -f $stats.ResourceGroups; Caption = 'resource groups'; Color = 'grey70' } @{ Value = '{0:N0}' -f $stats.Resources; Caption = 'resources'; Color = 'deepskyblue1' } @{ Value = '{0:N0}' -f $stats.ResourceTypes; Caption = 'resource types'; Color = 'mediumpurple2' } if ($null -ne $stats.Advisor) { @{ Value = '{0:N0}' -f $stats.AdvisorHigh; Caption = 'Advisor High'; Color = $(if ($stats.AdvisorHigh) { 'red1' } else { 'green3' }) } } @{ Value = '{0:N0}' -f $stats.Retirements; Caption = 'retiring features'; Color = $(if ($stats.Retirements) { 'orange1' } else { 'green3' }) } if ($null -ne $stats.Security) { @{ Value = '{0:N0}' -f $stats.SecurityHigh; Caption = 'Defender High'; Color = $(if ($stats.SecurityHigh) { 'red1' } else { 'green3' }) } } @{ Value = '{0:N0}' -f $stats.Orphans; Caption = 'unattached / empty'; Color = $(if ($stats.Orphans) { 'orange1' } else { 'green3' }) } if ($null -ne $stats.CostMonthToDate) { @{ Value = '{0:N0} {1}' -f $stats.CostMonthToDate, $stats.Currency; Caption = 'cost this month'; Color = 'green3' } } ) [Spectre.Console.AnsiConsole]::WriteLine() # --- The inventory by category ---------------------------------------------------------------------------- $all = & $find 'All resources' $rows = @(if ($all) { $all.Rows }) if ($rows.Count) { $table = [Spectre.Console.Table]::new() $table.Border = [Spectre.Console.TableBorder]::Rounded $table.BorderStyle = [Spectre.Console.Style]::Parse('deepskyblue3_1') $table.Expand = $true $table.Title = [Spectre.Console.TableTitle]::new("[bold]$($glyph.Bullet) Inventory by category[/]") foreach ($header in 'Category', 'Resources', 'Sheets', 'Most common types') { $column = [Spectre.Console.TableColumn]::new("[grey62]$header[/]") if ($header -eq 'Resources') { $column.Alignment = [Spectre.Console.Justify]::Right } $table.AddColumn($column) | Out-Null } foreach ($group in $rows | Group-Object Category | Sort-Object Count -Descending) { $sheets = @($Assessment.Sheets | Where-Object { $_.Kind -eq 'Inventory' -and $_.Category -eq $group.Name -and @($_.Rows).Count }) $types = @($group.Group | Group-Object Type | Sort-Object Count -Descending | Select-Object -First 3 | ForEach-Object { "$(($_.Name -split '/')[-1]) ($($_.Count))" }) [Spectre.Console.TableExtensions]::AddRow($table, [Spectre.Console.Rendering.IRenderable[]]@( [Spectre.Console.Markup]::new("[white]$(& $escape $group.Name)[/]") [Spectre.Console.Markup]::new(('{0:N0}' -f $group.Count)) [Spectre.Console.Markup]::new("[grey70]$(& $escape ((@($sheets | ForEach-Object { $_.Sheet })) -join ', '))[/]") [Spectre.Console.Markup]::new("[grey58]$(& $escape ($types -join ', '))[/]") )) | Out-Null } [Spectre.Console.AnsiConsole]::Write($table) [Spectre.Console.AnsiConsole]::WriteLine() $top = @($rows | Group-Object Type | Sort-Object Count -Descending | Select-Object -First 12 | ForEach-Object { @{ Label = $_.Name; Value = $_.Count } }) Show-AACBarChart -Item $top -Title 'Most common resource types' [Spectre.Console.AnsiConsole]::WriteLine() } # --- What needs attention --------------------------------------------------------------------------------- $lines = [System.Collections.Generic.List[string]]::new() $retirements = & $find 'Retirements' foreach ($item in @(if ($retirements) { $retirements.Rows }) | Select-Object -First 5) { $lines.Add(" [orange1]RETIRING[/] [white]$(& $escape $item.Resource)[/] [grey50]$(& $escape $item.'Retirement date')[/] [grey70]$(& $escape $item.Retiring)[/]") } $advisor = & $find 'Advisor recommendations' foreach ($item in @(if ($advisor) { $advisor.Rows | Where-Object Impact -EQ 'High' }) | Select-Object -First 5) { $lines.Add(" [red1]ADVISOR [/] [white]$(& $escape $item.Resource)[/] [grey50]$(& $escape $item.Category)[/] [grey70]$(& $escape $item.Problem)[/]") } $security = & $find 'Security recommendations' foreach ($item in @(if ($security) { $security.Rows | Where-Object Severity -EQ 'High' }) | Select-Object -First 5) { $lines.Add(" [red1]DEFENDER[/] [white]$(& $escape $item.Resource)[/] [grey70]$(& $escape $item.Recommendation)[/]") } $outages = & $find 'Outages' foreach ($item in @(if ($outages) { $outages.Rows }) | Select-Object -First 3) { $lines.Add(" [orange1]OUTAGE [/] [white]$(& $escape $item.'Tracking ID')[/] [grey50]$(& $escape $item.Started)[/] [grey70]$(& $escape $item.Title)[/]") } foreach ($notice in @($Assessment.Notices)) { $lines.Add(" [orange1]NOT READ[/] [grey70]$(& $escape $notice)[/]") } if ($lines.Count) { Write-AACMarkup "[bold]$($glyph.Bullet) Needs attention[/] [grey58]$($glyph.Dot) the first few of each - the reports have them all[/]" foreach ($line in $lines) { Write-AACMarkup $line } [Spectre.Console.AnsiConsole]::WriteLine() } # --- The reports ---------------------------------------------------------------------------------------- $content = [System.Collections.Generic.List[string]]::new() $byKind = @($File | Where-Object { $_ } | Group-Object { switch -Wildcard ($_.Name) { '*.csv' { 'CSV'; break } '*-Network.html' { 'Diagram'; break } '*-Organization.html' { 'Diagram'; break } '*-Resources.html' { 'Diagram'; break } '*.html' { 'HTML'; break } '*.pdf' { 'PDF'; break } default { 'File' } } }) foreach ($group in $byKind) { if ($group.Name -eq 'CSV') { $content.Add("[grey58]CSV[/] $($group.Count) file(s) in [link]$(& $escape $group.Group[0].DirectoryName)[/]") } else { foreach ($item in $group.Group) { $content.Add("[grey58]$($group.Name.PadRight(8))[/] [link]$(& $escape $item.FullName)[/]") } } } if ($Timing -and $Timing.Count) { $content.Add("[grey58]Took[/] $((@($Timing.Keys | ForEach-Object { "$_ $('{0:mm\:ss}' -f $Timing[$_])" })) -join ' · ')") } if ($content.Count) { Show-AACPanel -Content ($content -join "`n") -Header 'Reports' -BorderColor 'green3' -AllowMarkup } } |