Merge branch 'main' of https://git.azuze.fr/kawa/Sharepoint-Toolbox
This commit is contained in:
@@ -2,7 +2,14 @@ using System.IO;
|
||||
using System.Text;
|
||||
using SharepointToolbox.Core.Models;
|
||||
using SharepointToolbox.Localization;
|
||||
<<<<<<< HEAD
|
||||
using static SharepointToolbox.Services.Export.PermissionHtmlFragments;
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
using static SharepointToolbox.Services.Export.PermissionHtmlFragments;
|
||||
=======
|
||||
>>>>>>> f4cc81bb71b935c6f6f050288c9e283dcca5cfa8
|
||||
>>>>>>> b8c09655ac1a3cf1e116d5b5178ec293659c1a72
|
||||
|
||||
namespace SharepointToolbox.Services.Export;
|
||||
|
||||
@@ -30,6 +37,10 @@ public class HtmlExportService
|
||||
IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null)
|
||||
{
|
||||
var T = TranslationSource.Instance;
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
>>>>>>> b8c09655ac1a3cf1e116d5b5178ec293659c1a72
|
||||
var (totalEntries, uniquePermSets, distinctUsers) = ComputeStats(
|
||||
entries.Count,
|
||||
entries.Select(e => e.PermissionLevels),
|
||||
@@ -43,6 +54,87 @@ public class HtmlExportService
|
||||
AppendStatsCards(sb, totalEntries, uniquePermSets, distinctUsers);
|
||||
AppendFilterInput(sb);
|
||||
AppendTableOpen(sb);
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
=======
|
||||
// Compute stats
|
||||
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();
|
||||
|
||||
// ── HTML HEAD ──────────────────────────────────────────────────────────
|
||||
sb.AppendLine("<!DOCTYPE html>");
|
||||
sb.AppendLine("<html lang=\"en\">");
|
||||
sb.AppendLine("<head>");
|
||||
sb.AppendLine("<meta charset=\"UTF-8\">");
|
||||
sb.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
|
||||
sb.AppendLine($"<title>{T["report.title.permissions"]}</title>");
|
||||
sb.AppendLine("<style>");
|
||||
sb.AppendLine(@"
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||||
h1 { padding: 20px 24px 10px; font-size: 1.5rem; color: #1a1a2e; }
|
||||
.stats { display: flex; gap: 16px; padding: 0 24px 16px; flex-wrap: wrap; }
|
||||
.stat-card { background: #fff; border-radius: 8px; padding: 14px 20px; min-width: 160px; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
|
||||
.stat-card .value { font-size: 2rem; font-weight: 700; color: #1a1a2e; }
|
||||
.stat-card .label { font-size: .8rem; color: #666; margin-top: 2px; }
|
||||
.filter-wrap { padding: 0 24px 12px; }
|
||||
#filter { width: 320px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: .95rem; }
|
||||
.table-wrap { overflow-x: auto; padding: 0 24px 32px; }
|
||||
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
|
||||
th { background: #1a1a2e; color: #fff; padding: 10px 14px; text-align: left; font-size: .85rem; white-space: nowrap; }
|
||||
td { padding: 9px 14px; border-bottom: 1px solid #eee; vertical-align: top; font-size: .875rem; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
tr:hover td { background: #fafafa; }
|
||||
/* Type badges */
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; white-space: nowrap; }
|
||||
.badge.site-coll { background: #dbeafe; color: #1e40af; }
|
||||
.badge.site { background: #dcfce7; color: #166534; }
|
||||
.badge.list { background: #fef9c3; color: #854d0e; }
|
||||
.badge.folder { background: #f3f4f6; color: #374151; }
|
||||
/* Unique/Inherited badges */
|
||||
.badge.unique { background: #dcfce7; color: #166534; }
|
||||
.badge.inherited { background: #f3f4f6; color: #374151; }
|
||||
/* User pills */
|
||||
.user-pill { display: inline-block; background: #e0e7ff; color: #3730a3; border-radius: 12px; padding: 2px 10px; font-size: .75rem; margin: 2px 3px 2px 0; white-space: nowrap; }
|
||||
.user-pill.external-user { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
|
||||
.group-expandable { cursor: pointer; }
|
||||
.group-expandable:hover { opacity: 0.8; }
|
||||
a { color: #2563eb; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
");
|
||||
sb.AppendLine("</style>");
|
||||
sb.AppendLine("</head>");
|
||||
|
||||
// ── BODY ───────────────────────────────────────────────────────────────
|
||||
sb.AppendLine("<body>");
|
||||
sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding));
|
||||
sb.AppendLine($"<h1>{T["report.title.permissions"]}</h1>");
|
||||
|
||||
// Stats cards
|
||||
sb.AppendLine("<div class=\"stats\">");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{totalEntries}</div><div class=\"label\">{T["report.stat.total_entries"]}</div></div>");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{uniquePermSets}</div><div class=\"label\">{T["report.stat.unique_permission_sets"]}</div></div>");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{distinctUsers}</div><div class=\"label\">{T["report.stat.distinct_users_groups"]}</div></div>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Filter input
|
||||
sb.AppendLine("<div class=\"filter-wrap\">");
|
||||
sb.AppendLine($" <input type=\"text\" id=\"filter\" placeholder=\"{T["report.filter.placeholder_permissions"]}\" onkeyup=\"filterTable()\" />");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Table
|
||||
sb.AppendLine("<div class=\"table-wrap\">");
|
||||
sb.AppendLine("<table id=\"permTable\">");
|
||||
>>>>>>> f4cc81bb71b935c6f6f050288c9e283dcca5cfa8
|
||||
>>>>>>> b8c09655ac1a3cf1e116d5b5178ec293659c1a72
|
||||
sb.AppendLine("<thead><tr>");
|
||||
sb.AppendLine($" <th>{T["report.col.object"]}</th><th>{T["report.col.title"]}</th><th>{T["report.col.url"]}</th><th>{T["report.badge.unique"]}</th><th>{T["report.col.users_groups"]}</th><th>{T["report.col.permission_level"]}</th><th>{T["report.col.granted_through"]}</th>");
|
||||
sb.AppendLine("</tr></thead>");
|
||||
@@ -55,9 +147,58 @@ public class HtmlExportService
|
||||
var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited";
|
||||
var uniqueLbl = entry.HasUniquePermissions ? T["report.badge.unique"] : T["report.badge.inherited"];
|
||||
|
||||
<<<<<<< HEAD
|
||||
var (pills, subRows) = BuildUserPillsCell(
|
||||
entry.UserLogins, entry.Users, entry.PrincipalType, groupMembers,
|
||||
colSpan: 7, grpMemIdx: ref grpMemIdx);
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
var (pills, subRows) = BuildUserPillsCell(
|
||||
entry.UserLogins, entry.Users, entry.PrincipalType, groupMembers,
|
||||
colSpan: 7, grpMemIdx: ref grpMemIdx);
|
||||
=======
|
||||
// Build user pills: zip UserLogins and Users (both semicolon-delimited)
|
||||
var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
||||
var names = entry.Users.Split(';', StringSplitOptions.RemoveEmptyEntries);
|
||||
var pillsBuilder = new StringBuilder();
|
||||
var memberSubRows = 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);
|
||||
|
||||
bool isExpandableGroup = entry.PrincipalType == "SharePointGroup"
|
||||
&& groupMembers != null
|
||||
&& groupMembers.TryGetValue(name, out var members);
|
||||
|
||||
if (isExpandableGroup && groupMembers != null && groupMembers.TryGetValue(name, out var resolvedMembers))
|
||||
{
|
||||
var grpId = $"grpmem{grpMemIdx}";
|
||||
pillsBuilder.Append($"<span class=\"user-pill group-expandable\" onclick=\"toggleGroup('{grpId}')\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)} ▼</span>");
|
||||
|
||||
string memberContent;
|
||||
if (resolvedMembers.Count > 0)
|
||||
{
|
||||
var memberParts = resolvedMembers.Select(m => $"{HtmlEncode(m.DisplayName)} <{HtmlEncode(m.Login)}>");
|
||||
memberContent = string.Join(" • ", memberParts);
|
||||
}
|
||||
else
|
||||
{
|
||||
memberContent = $"<em style=\"color:#888\">{T["report.text.members_unavailable"]}</em>";
|
||||
}
|
||||
memberSubRows.AppendLine($"<tr data-group=\"{grpId}\" style=\"display:none\"><td colspan=\"7\" style=\"padding-left:2em;font-size:.8rem;color:#555\">{memberContent}</td></tr>");
|
||||
grpMemIdx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pillCss = isExt ? "user-pill external-user" : "user-pill";
|
||||
pillsBuilder.Append($"<span class=\"{pillCss}\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)}</span>");
|
||||
}
|
||||
}
|
||||
>>>>>>> f4cc81bb71b935c6f6f050288c9e283dcca5cfa8
|
||||
>>>>>>> b8c09655ac1a3cf1e116d5b5178ec293659c1a72
|
||||
|
||||
sb.AppendLine("<tr>");
|
||||
sb.AppendLine($" <td><span class=\"{typeCss}\">{HtmlEncode(entry.ObjectType)}</span></td>");
|
||||
@@ -291,4 +432,235 @@ public class HtmlExportService
|
||||
RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"),
|
||||
_ => ("#F3F4F6", "#374151", "#E5E7EB")
|
||||
};
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
|
||||
/// <summary>
|
||||
/// Builds a self-contained HTML string from simplified permission entries.
|
||||
/// Includes risk-level summary cards, color-coded rows, and simplified labels column.
|
||||
/// When <paramref name="groupMembers"/> is provided, SharePoint group pills become expandable.
|
||||
/// </summary>
|
||||
public string BuildHtml(IReadOnlyList<SimplifiedPermissionEntry> entries, ReportBranding? branding = null,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null)
|
||||
{
|
||||
var T = TranslationSource.Instance;
|
||||
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("<!DOCTYPE html>");
|
||||
sb.AppendLine("<html lang=\"en\">");
|
||||
sb.AppendLine("<head>");
|
||||
sb.AppendLine("<meta charset=\"UTF-8\">");
|
||||
sb.AppendLine("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">");
|
||||
sb.AppendLine($"<title>{T["report.title.permissions_simplified"]}</title>");
|
||||
sb.AppendLine("<style>");
|
||||
sb.AppendLine(@"
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; color: #333; }
|
||||
h1 { padding: 20px 24px 10px; font-size: 1.5rem; color: #1a1a2e; }
|
||||
.stats { display: flex; gap: 16px; padding: 0 24px 16px; flex-wrap: wrap; }
|
||||
.stat-card { background: #fff; border-radius: 8px; padding: 14px 20px; min-width: 160px; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
|
||||
.stat-card .value { font-size: 2rem; font-weight: 700; color: #1a1a2e; }
|
||||
.stat-card .label { font-size: .8rem; color: #666; margin-top: 2px; }
|
||||
.risk-cards { display: flex; gap: 12px; padding: 0 24px 16px; flex-wrap: wrap; }
|
||||
.risk-card { border-radius: 8px; padding: 12px 18px; min-width: 140px; border: 1px solid; }
|
||||
.risk-card .count { font-size: 1.5rem; font-weight: 700; }
|
||||
.risk-card .rlabel { font-size: .8rem; margin-top: 2px; }
|
||||
.risk-card .users { font-size: .7rem; margin-top: 2px; opacity: 0.8; }
|
||||
.filter-wrap { padding: 0 24px 12px; }
|
||||
#filter { width: 320px; padding: 8px 12px; border: 1px solid #ccc; border-radius: 6px; font-size: .95rem; }
|
||||
.table-wrap { overflow-x: auto; padding: 0 24px 32px; }
|
||||
table { width: 100%; border-collapse: collapse; background: #fff; border-radius: 8px; overflow: hidden; box-shadow: 0 1px 4px rgba(0,0,0,.1); }
|
||||
th { background: #1a1a2e; color: #fff; padding: 10px 14px; text-align: left; font-size: .85rem; white-space: nowrap; }
|
||||
td { padding: 9px 14px; border-bottom: 1px solid #eee; vertical-align: top; font-size: .875rem; }
|
||||
tr:last-child td { border-bottom: none; }
|
||||
tr:hover td { background: rgba(0,0,0,.03); }
|
||||
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; white-space: nowrap; }
|
||||
.badge.site-coll { background: #dbeafe; color: #1e40af; }
|
||||
.badge.site { background: #dcfce7; color: #166534; }
|
||||
.badge.list { background: #fef9c3; color: #854d0e; }
|
||||
.badge.folder { background: #f3f4f6; color: #374151; }
|
||||
.badge.unique { background: #dcfce7; color: #166534; }
|
||||
.badge.inherited { background: #f3f4f6; color: #374151; }
|
||||
.risk-badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: .75rem; font-weight: 600; border: 1px solid; }
|
||||
.user-pill { display: inline-block; background: #e0e7ff; color: #3730a3; border-radius: 12px; padding: 2px 10px; font-size: .75rem; margin: 2px 3px 2px 0; white-space: nowrap; }
|
||||
.user-pill.external-user { background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
|
||||
.group-expandable { cursor: pointer; }
|
||||
.group-expandable:hover { opacity: 0.8; }
|
||||
a { color: #2563eb; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
");
|
||||
sb.AppendLine("</style>");
|
||||
sb.AppendLine("</head>");
|
||||
|
||||
sb.AppendLine("<body>");
|
||||
sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding));
|
||||
sb.AppendLine($"<h1>{T["report.title.permissions_simplified"]}</h1>");
|
||||
|
||||
// Stats cards
|
||||
sb.AppendLine("<div class=\"stats\">");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{totalEntries}</div><div class=\"label\">{T["report.stat.total_entries"]}</div></div>");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{uniquePermSets}</div><div class=\"label\">{T["report.stat.unique_permission_sets"]}</div></div>");
|
||||
sb.AppendLine($" <div class=\"stat-card\"><div class=\"value\">{distinctUsers}</div><div class=\"label\">{T["report.stat.distinct_users_groups"]}</div></div>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Risk-level summary cards
|
||||
sb.AppendLine("<div class=\"risk-cards\">");
|
||||
foreach (var summary in summaries)
|
||||
{
|
||||
var (bg, text, border) = RiskLevelColors(summary.RiskLevel);
|
||||
sb.AppendLine($" <div class=\"risk-card\" style=\"background:{bg};color:{text};border-color:{border}\">");
|
||||
sb.AppendLine($" <div class=\"count\">{summary.Count}</div>");
|
||||
sb.AppendLine($" <div class=\"rlabel\">{HtmlEncode(summary.Label)}</div>");
|
||||
sb.AppendLine($" <div class=\"users\">{summary.DistinctUsers} user(s)</div>");
|
||||
sb.AppendLine(" </div>");
|
||||
}
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Filter input
|
||||
sb.AppendLine("<div class=\"filter-wrap\">");
|
||||
sb.AppendLine($" <input type=\"text\" id=\"filter\" placeholder=\"{T["report.filter.placeholder_permissions"]}\" onkeyup=\"filterTable()\" />");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
// Table with simplified columns
|
||||
sb.AppendLine("<div class=\"table-wrap\">");
|
||||
sb.AppendLine("<table id=\"permTable\">");
|
||||
sb.AppendLine("<thead><tr>");
|
||||
sb.AppendLine($" <th>{T["report.col.object"]}</th><th>{T["report.col.title"]}</th><th>{T["report.col.url"]}</th><th>{T["report.badge.unique"]}</th><th>{T["report.col.users_groups"]}</th><th>{T["report.col.permission_level"]}</th><th>{T["report.col.simplified"]}</th><th>{T["report.col.risk"]}</th><th>{T["report.col.granted_through"]}</th>");
|
||||
sb.AppendLine("</tr></thead>");
|
||||
sb.AppendLine("<tbody>");
|
||||
|
||||
int grpMemIdx = 0;
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
var typeCss = ObjectTypeCss(entry.ObjectType);
|
||||
var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited";
|
||||
var uniqueLbl = entry.HasUniquePermissions ? T["report.badge.unique"] : T["report.badge.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();
|
||||
var memberSubRows = 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);
|
||||
|
||||
bool isExpandableGroup = entry.Inner.PrincipalType == "SharePointGroup"
|
||||
&& groupMembers != null
|
||||
&& groupMembers.TryGetValue(name, out _);
|
||||
|
||||
if (isExpandableGroup && groupMembers != null && groupMembers.TryGetValue(name, out var resolvedMembers))
|
||||
{
|
||||
var grpId = $"grpmem{grpMemIdx}";
|
||||
pillsBuilder.Append($"<span class=\"user-pill group-expandable\" onclick=\"toggleGroup('{grpId}')\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)} ▼</span>");
|
||||
|
||||
string memberContent;
|
||||
if (resolvedMembers.Count > 0)
|
||||
{
|
||||
var memberParts = resolvedMembers.Select(m => $"{HtmlEncode(m.DisplayName)} <{HtmlEncode(m.Login)}>");
|
||||
memberContent = string.Join(" • ", memberParts);
|
||||
}
|
||||
else
|
||||
{
|
||||
memberContent = $"<em style=\"color:#888\">{T["report.text.members_unavailable"]}</em>";
|
||||
}
|
||||
memberSubRows.AppendLine($"<tr data-group=\"{grpId}\" style=\"display:none\"><td colspan=\"9\" style=\"padding-left:2em;font-size:.8rem;color:#555\">{memberContent}</td></tr>");
|
||||
grpMemIdx++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pillCss = isExt ? "user-pill external-user" : "user-pill";
|
||||
pillsBuilder.Append($"<span class=\"{pillCss}\" data-email=\"{HtmlEncode(login)}\">{HtmlEncode(name)}</span>");
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine("<tr>");
|
||||
sb.AppendLine($" <td><span class=\"{typeCss}\">{HtmlEncode(entry.ObjectType)}</span></td>");
|
||||
sb.AppendLine($" <td>{HtmlEncode(entry.Title)}</td>");
|
||||
sb.AppendLine($" <td><a href=\"{HtmlEncode(entry.Url)}\" target=\"_blank\">{T["report.text.link"]}</a></td>");
|
||||
sb.AppendLine($" <td><span class=\"{uniqueCss}\">{uniqueLbl}</span></td>");
|
||||
sb.AppendLine($" <td>{pillsBuilder}</td>");
|
||||
sb.AppendLine($" <td>{HtmlEncode(entry.PermissionLevels)}</td>");
|
||||
sb.AppendLine($" <td>{HtmlEncode(entry.SimplifiedLabels)}</td>");
|
||||
sb.AppendLine($" <td><span class=\"risk-badge\" style=\"background:{riskBg};color:{riskText};border-color:{riskBorder}\">{HtmlEncode(entry.RiskLevel.ToString())}</span></td>");
|
||||
sb.AppendLine($" <td>{HtmlEncode(entry.GrantedThrough)}</td>");
|
||||
sb.AppendLine("</tr>");
|
||||
if (memberSubRows.Length > 0)
|
||||
sb.Append(memberSubRows);
|
||||
}
|
||||
|
||||
sb.AppendLine("</tbody>");
|
||||
sb.AppendLine("</table>");
|
||||
sb.AppendLine("</div>");
|
||||
|
||||
sb.AppendLine("<script>");
|
||||
sb.AppendLine(@"function filterTable() {
|
||||
var input = document.getElementById('filter').value.toLowerCase();
|
||||
var rows = document.querySelectorAll('#permTable tbody tr');
|
||||
rows.forEach(function(row) {
|
||||
if (row.hasAttribute('data-group')) return;
|
||||
row.style.display = row.textContent.toLowerCase().indexOf(input) > -1 ? '' : 'none';
|
||||
});
|
||||
}
|
||||
function toggleGroup(id) {
|
||||
var rows = document.querySelectorAll('tr[data-group=""' + id + '""]');
|
||||
rows.forEach(function(r) { r.style.display = r.style.display === 'none' ? '' : 'none'; });
|
||||
}");
|
||||
sb.AppendLine("</script>");
|
||||
sb.AppendLine("</body>");
|
||||
sb.AppendLine("</html>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the simplified HTML report to the specified file path.
|
||||
/// </summary>
|
||||
public async Task WriteAsync(IReadOnlyList<SimplifiedPermissionEntry> entries, string filePath, CancellationToken ct,
|
||||
ReportBranding? branding = null,
|
||||
IReadOnlyDictionary<string, IReadOnlyList<ResolvedMember>>? groupMembers = null)
|
||||
{
|
||||
var html = BuildHtml(entries, branding, groupMembers);
|
||||
await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct);
|
||||
}
|
||||
|
||||
/// <summary>Returns the CSS class for the object-type badge.</summary>
|
||||
private static string ObjectTypeCss(string t) => t switch
|
||||
{
|
||||
"Site Collection" => "badge site-coll",
|
||||
"Site" => "badge site",
|
||||
"List" => "badge list",
|
||||
"Folder" => "badge folder",
|
||||
_ => "badge"
|
||||
};
|
||||
|
||||
/// <summary>Minimal HTML encoding for text content and attribute values.</summary>
|
||||
private static string HtmlEncode(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return string.Empty;
|
||||
return value
|
||||
.Replace("&", "&")
|
||||
.Replace("<", "<")
|
||||
.Replace(">", ">")
|
||||
.Replace("\"", """)
|
||||
.Replace("'", "'");
|
||||
}
|
||||
>>>>>>> f4cc81bb71b935c6f6f050288c9e283dcca5cfa8
|
||||
>>>>>>> b8c09655ac1a3cf1e116d5b5178ec293659c1a72
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user