diff --git a/SharepointToolbox/Services/Export/HtmlExportService.cs b/SharepointToolbox/Services/Export/HtmlExportService.cs index eb5e14f..9553c96 100644 --- a/SharepointToolbox/Services/Export/HtmlExportService.cs +++ b/SharepointToolbox/Services/Export/HtmlExportService.cs @@ -154,6 +154,175 @@ a:hover { text-decoration: underline; } await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct); } + /// Returns inline CSS background and text color for a risk level. + private static (string bg, string text, string border) RiskLevelColors(RiskLevel level) => level switch + { + RiskLevel.High => ("#FEE2E2", "#991B1B", "#FECACA"), + RiskLevel.Medium => ("#FEF3C7", "#92400E", "#FDE68A"), + RiskLevel.Low => ("#D1FAE5", "#065F46", "#A7F3D0"), + RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"), + _ => ("#F3F4F6", "#374151", "#E5E7EB") + }; + + /// + /// Builds a self-contained HTML string from simplified permission entries. + /// Includes risk-level summary cards, color-coded rows, and simplified labels column. + /// + public string BuildHtml(IReadOnlyList entries) + { + var summaries = PermissionSummaryBuilder.Build(entries); + + var totalEntries = entries.Count; + var uniquePermSets = entries.Select(e => e.PermissionLevels).Distinct().Count(); + var distinctUsers = entries + .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)) + .Select(u => u.Trim()) + .Where(u => u.Length > 0) + .Distinct() + .Count(); + + var sb = new StringBuilder(); + + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine("SharePoint Permissions Report (Simplified)"); + sb.AppendLine(""); + sb.AppendLine(""); + + sb.AppendLine(""); + sb.AppendLine("

SharePoint Permissions Report (Simplified)

"); + + // Stats cards + sb.AppendLine("
"); + sb.AppendLine($"
{totalEntries}
Total Entries
"); + sb.AppendLine($"
{uniquePermSets}
Unique Permission Sets
"); + sb.AppendLine($"
{distinctUsers}
Distinct Users/Groups
"); + sb.AppendLine("
"); + + // Risk-level summary cards + sb.AppendLine("
"); + foreach (var summary in summaries) + { + var (bg, text, border) = RiskLevelColors(summary.RiskLevel); + sb.AppendLine($"
"); + sb.AppendLine($"
{summary.Count}
"); + sb.AppendLine($"
{HtmlEncode(summary.Label)}
"); + sb.AppendLine($"
{summary.DistinctUsers} user(s)
"); + sb.AppendLine("
"); + } + sb.AppendLine("
"); + + // Filter input + sb.AppendLine("
"); + sb.AppendLine(" "); + sb.AppendLine("
"); + + // Table with simplified columns + sb.AppendLine("
"); + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(" "); + sb.AppendLine(""); + sb.AppendLine(""); + + foreach (var entry in entries) + { + var typeCss = ObjectTypeCss(entry.ObjectType); + var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited"; + var uniqueLbl = entry.HasUniquePermissions ? "Unique" : "Inherited"; + var (riskBg, riskText, riskBorder) = RiskLevelColors(entry.RiskLevel); + + var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries); + var names = entry.Inner.Users.Split(';', StringSplitOptions.RemoveEmptyEntries); + var pillsBuilder = new StringBuilder(); + for (int i = 0; i < logins.Length; i++) + { + var login = logins[i].Trim(); + var name = i < names.Length ? names[i].Trim() : login; + var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase); + var pillCss = isExt ? "user-pill external-user" : "user-pill"; + pillsBuilder.Append($"{HtmlEncode(name)}"); + } + + sb.AppendLine(""); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine($" "); + sb.AppendLine(""); + } + + sb.AppendLine(""); + sb.AppendLine("
ObjectTitleURLUniqueUsers/GroupsPermission LevelSimplifiedRiskGranted Through
{HtmlEncode(entry.ObjectType)}{HtmlEncode(entry.Title)}Link{uniqueLbl}{pillsBuilder}{HtmlEncode(entry.PermissionLevels)}{HtmlEncode(entry.SimplifiedLabels)}{HtmlEncode(entry.RiskLevel.ToString())}{HtmlEncode(entry.GrantedThrough)}
"); + sb.AppendLine("
"); + + sb.AppendLine(""); + sb.AppendLine(""); + sb.AppendLine(""); + + return sb.ToString(); + } + + /// + /// Writes the simplified HTML report to the specified file path. + /// + public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct) + { + var html = BuildHtml(entries); + await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct); + } + /// Returns the CSS class for the object-type badge. private static string ObjectTypeCss(string t) => t switch {