mail-organizer/scripts/Generate-ProtocolHtml.ps1

168 lines
8.1 KiB
PowerShell

param(
[Parameter(Mandatory)][string]$JsonData,
[Parameter(Mandatory)][string]$OutputPath
)
# Escape for safe embedding in JS string literal
$escaped = $JsonData -replace '\\', '\\\\' -replace "'", "\\\'" -replace "`r`n", '\n' -replace "`n", '\n'
$html = @'
<!DOCTYPE html>
<html lang="de"><head><meta charset="UTF-8"><title>Mail-Organizer Protokoll</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:Segoe UI,system-ui,sans-serif;background:#1e1e1e;color:#d4d4d4;padding:24px;max-width:1100px;margin:0 auto}
h1{font-size:1.3em;margin-bottom:4px;color:#e0e0e0}
.sub{color:#999;font-size:.85em;margin-bottom:20px}
.ctl{display:flex;gap:12px;align-items:center;margin-bottom:16px;flex-wrap:wrap}
.ctl label{font-weight:600;font-size:.82em;color:#ccc}
select{padding:5px 10px;border:1px solid #444;border-radius:4px;font-size:.82em;background:#2d2d2d;color:#d4d4d4}
.sbar{display:flex;gap:16px;background:#252526;border:1px solid #333;border-radius:6px;padding:14px 18px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
.sbar .it{font-size:.85em}.sbar .it strong{font-size:1.1em;color:#e0e0e0}
.b{display:inline-block;padding:2px 8px;border-radius:10px;font-size:.72em;font-weight:600;color:#fff;vertical-align:middle}
.bok{background:#2ea043}.ber{background:#d13438}.bwa{background:#ca5010}.bdr{background:#8764b8}.bin{background:#2b88d8}
.rc{background:#252526;border:1px solid #333;border-radius:6px;margin-bottom:8px;overflow:hidden}
.rh{padding:11px 16px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;user-select:none}
.rh:hover{background:#2a2d2e}.rh h3{font-size:.9em;font-weight:600;color:#e0e0e0}.rh .tg{display:flex;gap:5px}
.rb{display:none;border-top:1px solid #333}.rb.open{display:block}
.mr{padding:9px 16px;border-bottom:1px solid #333;font-size:.83em}.mr:last-child{border-bottom:none}
.ms{font-weight:600;color:#e0e0e0}.mm{color:#888;font-size:.92em;margin-top:1px}
.ac{margin-top:5px;display:flex;flex-wrap:wrap;gap:5px}
.at{display:inline-block;padding:2px 7px;border-radius:3px;font-size:.78em}
.at.ok{background:#1a3a1a;color:#4ec94e}.at.error{background:#3a1a1a;color:#f48771}
.at.dryrun{background:#2a1f3a;color:#b48eda}.at.warn{background:#3a2a1a;color:#e8a33d}
.nm{padding:12px 16px;color:#666;font-size:.83em;font-style:italic}
.em{color:#f48771;font-size:.83em;padding:8px 16px}
a.sl{color:#4daafc;text-decoration:none}a.sl:hover{text-decoration:underline}
.err-box{background:#3a1a1a;color:#f48771;border:1px solid #d13438;border-radius:6px;padding:16px;margin:20px 0;font-size:.9em}
</style></head><body>
<h1>Mail-Organizer Protokoll</h1>
<div class="sub" id="gen"></div>
<div class="ctl"><label>Lauf:</label><select id="rs"></select><label>Status:</label>
<select id="fl"><option value="all">Alle</option><option value="ok">Nur OK</option><option value="error">Nur Fehler</option><option value="dryrun">Nur DryRun</option></select></div>
<div id="o"></div>
<script>
var DATA;
try {
DATA = JSON.parse('
'@
$html += $escaped
$html += @'
');
} catch(ex) {
document.getElementById('o').innerHTML = '<div class="err-box"><strong>Fehler beim Laden der Protokolldaten:</strong><br>' + ex.message + '</div>';
}
if (DATA && DATA.length) {
var sel = document.getElementById('rs');
var flt = document.getElementById('fl');
var out = document.getElementById('o');
document.getElementById('gen').textContent = 'Generiert: ' + new Date().toLocaleString('de-DE');
DATA.sort(function(a,b) { return b.timestamp.localeCompare(a.timestamp); });
for (var i = 0; i < DATA.length; i++) {
var r = DATA[i];
var opt = document.createElement('option');
opt.value = i;
var d = r.dryRun ? ' [DRY RUN]' : '';
var e = r.summary.errors > 0 ? ' \u26A0 ' + r.summary.errors + ' Fehler' : '';
opt.textContent = r.timestamp + d + ' \u2014 ' + r.summary.processed + ' Mail(s)' + e;
sel.appendChild(opt);
}
sel.addEventListener('change', render);
flt.addEventListener('change', render);
render();
}
function render() {
var r = DATA[sel.value];
if (!r) return;
var f = flt.value;
var h = '<div class="sbar">';
h += '<div class="it">' + (r.dryRun ? '<span class="b bdr">DRY RUN</span> ' : '') + '<strong>' + esc(r.timestamp) + '</strong></div>';
h += '<div class="it">Mailbox: <strong>' + esc(r.mailbox) + '</strong></div>';
h += '<div class="it">Verarbeitet: <strong>' + r.summary.processed + '</strong></div>';
h += '<div class="it">Alerts: <strong>' + r.summary.alerts + '</strong></div>';
h += '<div class="it">' + (r.summary.errors > 0 ? '<span class="b ber">' + r.summary.errors + ' Fehler</span>' : '<span class="b bok">Fehlerfrei</span>') + '</div>';
h += '</div>';
var rules = r.rules || [];
for (var ri = 0; ri < rules.length; ri++) {
var rule = rules[ri];
var mails = fm(rule.mails || [], f);
var total = (rule.mails || []).length;
var errs = 0;
for (var ei = 0; ei < (rule.mails||[]).length; ei++) { if ((rule.mails||[])[ei].status === 'error') errs++; }
var badge = rule.error ? '<span class="b ber">API-Fehler</span>' : errs > 0 ? '<span class="b bwa">' + errs + '/' + total + ' Fehler</span>' : total > 0 ? '<span class="b bok">' + total + ' OK</span>' : '<span class="b bin">0 Mails</span>';
h += '<div class="rc"><div class="rh" onclick="tgl(this)"><h3>' + esc(rule.ruleName) + '</h3>';
h += '<div class="tg"><span class="b bin">' + total + '</span>' + badge + '</div></div>';
h += '<div class="rb' + (total > 0 || rule.error ? ' open' : '') + '">';
if (rule.error) h += '<div class="em">' + esc(rule.error) + '</div>';
if (mails.length === 0 && !rule.error) h += '<div class="nm">' + (f !== 'all' ? 'Keine Mails mit Filter "' + f + '"' : 'Keine passenden E-Mails') + '</div>';
for (var mi = 0; mi < mails.length; mi++) {
var m = mails[mi];
var dt = m.received ? new Date(m.received).toLocaleString('de-DE') : '';
h += '<div class="mr"><div class="ms">' + esc(m.subject) + '</div>';
h += '<div class="mm">' + esc(m.from) + ' &middot; ' + dt + '</div>';
h += '<div class="ac">' + acHtml(m.actions) + '</div></div>';
}
h += '</div></div>';
}
out.innerHTML = h;
}
function fm(mails, f) {
if (f === 'all') return mails;
var res = [];
for (var i = 0; i < mails.length; i++) {
var m = mails[i];
if (f === 'error' && m.status === 'error') res.push(m);
if (f === 'ok' && m.status === 'ok') res.push(m);
if (f === 'dryrun') { for (var j=0;j<(m.actions||[]).length;j++) { if(m.actions[j].status==='dryrun'){res.push(m);break;} } }
}
return f === 'all' ? mails : res;
}
function acHtml(actions) {
if (!actions || !actions.length) return '<span style="color:#aaa;font-size:.78em">Keine Aktionen</span>';
var parts = [];
for (var i = 0; i < actions.length; i++) {
var a = actions[i], l = a.action, d = '';
if (a.action === 'uploadToSharePoint') {
l = 'SharePoint';
if (a.files && a.files.length) {
var fparts = [];
for (var fi = 0; fi < a.files.length; fi++) {
var ff = a.files[fi];
var name = typeof ff === 'string' ? ff : (ff.name || '');
var url = (typeof ff === 'object' && ff !== null) ? ff.url : null;
fparts.push(url ? '<a class="sl" href="' + esc(url) + '" target="_blank">' + esc(name) + '</a>' : esc(name));
}
d = fparts.join(', ');
} else if (a.target) d = a.target;
} else if (a.action === 'saveAttachments') {
l = 'Anhaenge'; d = a.files ? a.files + ' Datei(en)' : '';
} else if (a.action === 'onSuccess') {
l = 'Erfolg'; d = (a.details || []).join(', ');
} else if (a.action === 'alert') {
l = 'Alert'; d = a.to || '';
}
if (a.error) d = a.error;
if (a.message) d = a.message;
parts.push('<span class="at ' + (a.status || 'ok') + '">' + esc(l) + (d ? ': ' + d : '') + '</span>');
}
return parts.join('');
}
function tgl(el) { var b = el.nextElementSibling; b.className = b.className.indexOf('open') >= 0 ? 'rb' : 'rb open'; }
function esc(s) { if (!s) return ''; var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
</script></body></html>
'@
# Write without BOM
$utf8NoBom = New-Object System.Text.UTF8Encoding $false
[System.IO.File]::WriteAllText($OutputPath, $html, $utf8NoBom)