Private/Report.ps1
|
<#
Rendu HTML du rapport de migration. Page autonome (CSS inline, aucune ressource externe) : elle doit rester lisible transmise par courriel ou déposée sur un partage. #> function ConvertTo-SPMHtmlText { [CmdletBinding()] [OutputType([string])] param([AllowNull()][string]$Text) if ($null -eq $Text) { return '' } [System.Net.WebUtility]::HtmlEncode($Text) } function Write-SPMHtmlReport { [CmdletBinding()] param( [Parameter(Mandatory)][pscustomobject]$Report, [Parameter(Mandatory)][string]$Path ) $statusColor = @{ Completed = '#1a7f37' Failed = '#cf222e' Skipped = '#9a6700' Running = '#0969da' Submitted = '#8250df' Pending = '#57606a' } # Une valeur absente n'est pas zéro. Afficher « 0,0 s » sur une durée jamais # chronométrée fait conclure à un transfert instantané. $duree = { param($ms) if ($null -eq $ms) { 'non mesuré' } else { "$([math]::Round($ms / 1000, 1)) s" } } # Le bandeau prime sur les cartes : c'est la première chose que doit lire # celui qui reçoit ce fichier par courriel. $alertes = @() if ($Report.PSObject.Properties['Alerts']) { $alertes = @($Report.Alerts) } $alertBlock = if ($alertes.Count) { $items = ($alertes | ForEach-Object { "<li>$(ConvertTo-SPMHtmlText $_)</li>" }) -join "`n" @" <div class="alert"> <div class="alert-title">Ce rapport doit être lu avec ces réserves</div> <ul> $items </ul> </div> "@ } else { '' } $rows = foreach ($s in $Report.StatusBreak) { $color = if ($statusColor.ContainsKey($s.Status)) { $statusColor[$s.Status] } else { '#57606a' } "<tr><td><span class='dot' style='background:$color'></span>$(ConvertTo-SPMHtmlText $s.Status)</td><td class='num'>$($s.Count)</td></tr>" } $failureRows = if ($Report.Failures.Count -gt 0) { foreach ($f in ($Report.Failures | Select-Object -First 200)) { "<tr><td class='mono'>$(ConvertTo-SPMHtmlText $f.Source)</td>" + "<td class='mono'>$(ConvertTo-SPMHtmlText $f.Target)</td>" + "<td class='err'>$(ConvertTo-SPMHtmlText $f.Error)</td></tr>" } } else { "<tr><td colspan='3' class='ok'>Aucun échec.</td></tr>" } $truncationNote = if ($Report.Failures.Count -gt 200) { "<p class='note'>Seuls les 200 premiers échecs sont affichés sur $($Report.Failures.Count).</p>" } else { '' } $throttleBlock = if ($Report.Throttling) { @" <div class="card"> <div class="label">Throttling absorbé</div> <div class="value">$($Report.Throttling.Events)</div> <div class="sub">$($Report.Throttling.WaitedSec) s d'attente respectée</div> </div> "@ } else { '' } $rateColor = if ($null -eq $Report.SuccessRate) { '#57606a' } elseif ($Report.SuccessRate -ge 99) { '#1a7f37' } elseif ($Report.SuccessRate -ge 90) { '#9a6700' } else { '#cf222e' } $rateValue = if ($null -eq $Report.SuccessRate) { 'non mesuré' } else { "$($Report.SuccessRate) %" } # La carte des échecs ne devient verte que si quelque chose a réellement # abouti. « 0 échec » sur 0 unité terminée n'est pas un succès. $rien = $Report.PSObject.Properties['NothingTransferred'] -and $Report.NothingTransferred $enCours = $Report.PSObject.Properties['Running'] -and $Report.Running $failColor = if ($Report.Failed -or $rien -or $enCours) { '#cf222e' } else { '#1a7f37' } $failSub = if ($Report.Failed) { 'relançables avec -Resume' } elseif ($rien) { 'aucun — mais rien n''a abouti' } elseif ($enCours) { 'aucun — mais l''exécution est incomplète' } else { 'aucun' } $submittedBlock = if ($Report.PSObject.Properties['Submitted'] -and $Report.Submitted) { @" <div class="card"> <div class="label">Soumises, non confirmées</div> <div class="value" style="color:#8250df">$($Report.Submitted)</div> <div class="sub">acceptées par le service, exécution non vérifiée</div> </div> "@ } else { '' } $html = @" <!doctype html> <html lang="fr"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Rapport de migration SharePoint</title> <style> :root { color-scheme: light dark; } body { font-family: -apple-system, "Segoe UI", system-ui, sans-serif; margin: 0; padding: 2rem; background: #f6f8fa; color: #1f2328; line-height: 1.5; } h1 { font-size: 1.5rem; margin: 0 0 .25rem; } .meta { color: #57606a; font-size: .875rem; margin-bottom: 1.5rem; } .cards { display: flex; flex-wrap: wrap; gap: 1rem; margin-bottom: 2rem; } .card { background: #fff; border: 1px solid #d1d9e0; border-radius: 8px; padding: 1rem 1.25rem; min-width: 150px; } .label { font-size: .75rem; text-transform: uppercase; letter-spacing: .04em; color: #57606a; } .value { font-size: 1.75rem; font-weight: 600; margin-top: .25rem; } .sub { font-size: .8125rem; color: #57606a; } table { width: 100%; border-collapse: collapse; background: #fff; border: 1px solid #d1d9e0; border-radius: 8px; overflow: hidden; margin-bottom: 1.5rem; } th, td { text-align: left; padding: .625rem .875rem; border-bottom: 1px solid #eaeef2; font-size: .875rem; } th { background: #f6f8fa; font-weight: 600; } tr:last-child td { border-bottom: none; } .num { text-align: right; font-variant-numeric: tabular-nums; } .mono { font-family: ui-monospace, "Cascadia Code", Menlo, monospace; font-size: .8125rem; word-break: break-all; } .err { color: #cf222e; font-size: .8125rem; } .ok { color: #1a7f37; } .note { color: #57606a; font-size: .8125rem; } .dot { display: inline-block; width: .625rem; height: .625rem; border-radius: 50%; margin-right: .5rem; } h2 { font-size: 1.125rem; margin: 1.5rem 0 .75rem; } .alert { background: #fff8f8; border: 1px solid #cf222e; border-left-width: 4px; border-radius: 8px; padding: .875rem 1.25rem; margin-bottom: 1.5rem; } .alert-title { font-weight: 600; color: #cf222e; margin-bottom: .375rem; } .alert ul { margin: 0; padding-left: 1.25rem; } .alert li { font-size: .875rem; } @media (prefers-color-scheme: dark) { body { background: #0d1117; color: #e6edf3; } .card, table { background: #161b22; border-color: #30363d; } th { background: #21262d; } th, td { border-bottom-color: #21262d; } .meta, .label, .sub, .note { color: #8b949e; } .alert { background: #2b1416; border-color: #f85149; } .alert-title { color: #f85149; } } </style> </head> <body> <h1>Rapport de migration SharePoint</h1> <div class="meta">Généré le $(ConvertTo-SPMHtmlText $Report.GeneratedAt) · état : <span class="mono">$(ConvertTo-SPMHtmlText $Report.StatePath)</span></div> $alertBlock <div class="cards"> <div class="card"> <div class="label">Taux de réussite</div> <div class="value" style="color:$rateColor">$rateValue</div> <div class="sub">$($Report.Completed) / $($Report.TotalUnits) unités</div> </div> <div class="card"> <div class="label">Échecs</div> <div class="value" style="color:$failColor">$($Report.Failed)</div> <div class="sub">$failSub</div> </div> <div class="card"> <div class="label">Ignorées</div> <div class="value">$($Report.Skipped)</div> <div class="sub">déjà à jour</div> </div> <div class="card"> <div class="label">Durée moyenne</div> <div class="value">$(& $duree $Report.AvgDurationMs)</div> <div class="sub">max $(& $duree $Report.MaxDurationMs)</div> </div> $submittedBlock $throttleBlock </div> <h2>Répartition par statut</h2> <table> <thead><tr><th>Statut</th><th class="num">Unités</th></tr></thead> <tbody> $($rows -join "`n") </tbody> </table> <h2>Échecs</h2> <table> <thead><tr><th>Source</th><th>Destination</th><th>Erreur</th></tr></thead> <tbody> $($failureRows -join "`n") </tbody> </table> $truncationNote </body> </html> "@ Set-Content -LiteralPath $Path -Value $html -Encoding utf8 } |