Private/Report.Assets.ps1
|
function Get-UTCMReportCss { # Shared stylesheet for the UTCM HTML reports. No external resources, no inline style attributes needed. [CmdletBinding()] param() $css = @' :root{color-scheme:light dark;--bg:#ffffff;--fg:#1b1f24;--muted:#4b5563;--line:#cfd6dd;--panel:#f3f5f7;--accent:#0b5cad;--focus:#0b5cad; --added-bg:#e3f4e8;--added-fg:#0b4a1f;--missing-bg:#fde8e6;--missing-fg:#82190f;--changed-bg:#fff2cf;--changed-fg:#5e4200;--code-bg:#f0f2f4} @media (prefers-color-scheme:dark){:root{--bg:#0d1117;--fg:#e6edf3;--muted:#a6b0bb;--line:#363d46;--panel:#161b22;--accent:#79b8ff;--focus:#79b8ff; --added-bg:#12301c;--added-fg:#86e8a6;--missing-bg:#3f1814;--missing-fg:#ffb8b0;--changed-bg:#3a2e0a;--changed-fg:#f5d37a;--code-bg:#161b22}} *{box-sizing:border-box} html{scroll-behavior:smooth} @media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}} body{margin:0;background:var(--bg);color:var(--fg);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif} .wrap{max-width:1400px;margin:0 auto;padding:16px} a{color:var(--accent)} code,pre{font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace} :focus-visible{outline:3px solid var(--focus);outline-offset:2px} .sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} .copy-ta{position:fixed;left:0;top:0;width:1px;height:1px;opacity:0} .skip{position:absolute;left:-999px;top:0;background:var(--bg);padding:8px;z-index:10} .skip:focus{left:8px;top:8px} h1{font-size:1.6rem;margin:0 0 4px} h2{font-size:1.2rem;margin:20px 0 8px} .meta{color:var(--muted);margin:4px 0;overflow-wrap:anywhere} .note{color:var(--muted);font-size:.9rem;margin:4px 0} .topnav{display:flex;flex-wrap:wrap;gap:6px 16px;margin:10px 0;padding:8px 0;border-bottom:1px solid var(--line)} .cards{list-style:none;padding:0;margin:12px 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px} .card{border:1px solid var(--line);border-radius:8px;padding:10px 12px;background:var(--panel)} .card .n{display:block;font-size:1.8rem;font-weight:700;line-height:1.1} .card .l{color:var(--muted);font-size:.85rem} .card.k-added{border-left:6px solid var(--added-fg)} .card.k-missing{border-left:6px dashed var(--missing-fg)} .card.k-changed{border-left:6px double var(--changed-fg)} .dist{list-style:none;padding:0;margin:6px 0;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px 32px} .dist li{display:grid;grid-template-columns:1fr 3em;gap:2px 8px;align-items:center} .dist .dn{grid-column:1 / -1;min-width:0;overflow-wrap:anywhere} .dist .bar{display:block;width:100%;height:14px} .dist .bar-bg{fill:var(--panel);stroke:var(--line);stroke-width:.6;vector-effect:non-scaling-stroke} .dist .bar-fg{fill:var(--accent)} .dist .dc{text-align:right;font-variant-numeric:tabular-nums} .toolbar{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:flex-end;margin:12px 0;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--panel)} .fld{display:flex;flex-direction:column;gap:2px;font-size:.85rem;color:var(--muted)} .fld.chk{flex-direction:row;align-items:center;gap:6px;color:var(--fg)} input[type=search],select{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:6px 8px;min-height:36px;max-width:100%} input[type=search]{width:100%} .fld.search{flex:1 1 20em} .rtable thead th .thl{display:inline-block;padding:2px 4px;min-height:24px;font-weight:700} button{font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:6px;padding:6px 10px;min-height:36px;cursor:pointer} button:hover{background:var(--panel)} button[aria-pressed=true]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);font-weight:600} .chips{display:flex;flex-wrap:wrap;gap:6px} .menu{position:relative} .menu>summary{cursor:pointer;border:1px solid var(--line);border-radius:6px;padding:6px 10px;min-height:36px;background:var(--bg);list-style:none} .menu>summary::-webkit-details-marker{display:none} .menu .menu-body{position:absolute;z-index:5;margin-top:4px;padding:10px;border:1px solid var(--line);border-radius:8px;background:var(--bg);display:flex;flex-direction:column;gap:6px;min-width:12em;box-shadow:0 4px 14px rgba(0,0,0,.25)} .menu .menu-body label{display:flex;gap:6px;align-items:center} .count{margin:8px 0;color:var(--muted)} .empty{padding:16px;border:1px dashed var(--line);border-radius:8px;margin:12px 0} .empty.ok{border-style:solid;background:var(--added-bg);color:var(--added-fg)} .rtable{width:100%;border-collapse:collapse;margin:6px 0} .rtable th,.rtable td{padding:8px 10px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line);overflow-wrap:anywhere} .rtable thead th{position:sticky;top:0;background:var(--panel);z-index:2;white-space:nowrap} .rtable thead th .sortbtn{border:0;background:transparent;padding:2px 4px;min-height:24px;font-weight:700} .rtable thead th[aria-sort=ascending] .sortbtn::after{content:" \25B2"} .rtable thead th[aria-sort=descending] .sortbtn::after{content:" \25BC"} .rtable tbody.item tr.detail td{padding-top:0;border-bottom:2px solid var(--line)} .rtable .nm{font-weight:600;overflow-wrap:anywhere} .rtable .idl{display:block;color:var(--muted);font-size:.85rem;overflow-wrap:anywhere} .filtered-out,.pg-hidden{display:none} .badge{display:inline-flex;gap:.35em;align-items:center;padding:1px 9px;border-radius:999px;font-weight:600;font-size:.85rem;white-space:nowrap} .st-added{background:var(--added-bg);color:var(--added-fg);border:2px solid var(--added-fg)} .st-missing{background:var(--missing-bg);color:var(--missing-fg);border:2px dashed var(--missing-fg)} .st-changed{background:var(--changed-bg);color:var(--changed-fg);border:3px double var(--changed-fg)} details>summary{cursor:pointer} details.rawwrap{margin-top:8px} pre.raw{background:var(--code-bg);padding:8px;border-radius:6px;white-space:pre-wrap;overflow-wrap:anywhere;max-height:420px;overflow:auto;font-size:.85rem;margin:4px 0} .tablewrap{overflow-x:auto;max-width:100%} .props{border-collapse:collapse;width:100%;font-size:.9rem;margin:6px 0} .props th,.props td{padding:4px 8px;border:1px solid var(--line);text-align:left;vertical-align:top} .props thead th{background:var(--panel)} .props .path{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-weight:600;overflow-wrap:anywhere} .props .v{display:inline-block;max-width:100%;max-height:12em;overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere} .props .absent{color:var(--muted);font-style:italic} .pst{white-space:nowrap;font-weight:600} .copybtn{min-height:24px;padding:0 6px;margin-left:6px;font-size:.75rem} .chg-print{display:none} .scope.changed-only .prop[data-st=unchanged]{display:none} .scope.hc-id [data-col=id],.scope.hc-type [data-col=type],.scope.hc-workload [data-col=workload],.scope.hc-changes [data-col=changes],.scope.hc-preview [data-col=preview]{display:none} .pager{display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center;margin:6px 0 14px} .pager .pg{color:var(--muted)} .wl-group{margin:10px 0;border:1px solid var(--line);border-radius:8px;padding:0 10px} .wl-group>summary{padding:8px 0;font-weight:700} .wl-group .gc{font-weight:400;color:var(--muted)} html:not(.js) .needs-js{display:none} [hidden]{display:none !important} @media (max-width:640px){ .wrap{padding:10px} .rtable,.rtable tbody.item,.rtable tr,.rtable td{display:block} .rtable thead{position:absolute;left:-9999px} .rtable tbody.item{border:1px solid var(--line);border-radius:8px;margin:0 0 10px;padding:4px 6px} .rtable tr.main td{display:flex;gap:8px;border:0;padding:3px 4px} .rtable tr.main td>*{min-width:0} .rtable tr.main td::before{content:attr(data-label);min-width:6.5em;color:var(--muted);font-weight:600} .rtable tbody.item tr.detail td{border-bottom:0} .toolbar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;align-items:stretch} .toolbar .fld.search,.toolbar .chips,.toolbar .fld.chk{grid-column:1 / -1} .menu .menu-body{position:static;box-shadow:none;min-width:0} .fld,input[type=search]{width:100%} button,select,.menu>summary{min-height:44px} } @media (forced-colors:active){.badge{forced-color-adjust:none;border-color:ButtonText;color:ButtonText;background:ButtonFace}.dist .bar-fg{fill:Highlight}.dist .bar-bg{fill:Canvas;stroke:CanvasText}} @media print{ body{background:#fff;color:#000;font-size:11pt} .needs-js,.toolbar,.pager,.topnav,.skip,.copybtn,.menu,.count{display:none !important} .wrap{max-width:none;padding:0} .pg-hidden{display:table-row-group} .filtered-out{display:none !important} .rtable thead{display:table-header-group} .rtable thead th{position:static} .rtable tbody.item{break-inside:avoid} .rtable tr.detail details,.rawwrap{display:none !important} .chg-print{display:block;margin:0;padding-left:18px;font-size:.85rem} .scope.changed-only .prop[data-st=unchanged]{display:none} .wl-group{border:0;padding:0} .badge,.card,.st-added,.st-missing,.st-changed{-webkit-print-color-adjust:exact;print-color-adjust:exact} a[href^=http]::after{content:" (" attr(href) ")";font-size:.8em;color:#444} #current-state{break-before:page} } '@ return ($css -replace "`r`n", "`n").Trim() } function Get-UTCMReportScript { # Shared, dependency-free client script (ES2017-safe). Operates on the report markup contract. [CmdletBinding()] param() $js = @' (function () { 'use strict'; var doc = document; doc.documentElement.classList.add('js'); var STATUS_RANK = { Changed: 0, Missing: 1, Added: 2 }; var SIZES = [25, 50, 100, 250]; var SORT_KEYS = ['status', 'name', 'type', 'workload', 'changes']; var COLS = ['id', 'type', 'workload', 'changes', 'preview']; var collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); var liveEl = doc.getElementById('live'); var currentSection = ''; var scopes = []; function q1(sel, ctx) { return (ctx || doc).querySelector(sel); } function qa(sel, ctx) { return Array.prototype.slice.call((ctx || doc).querySelectorAll(sel)); } function announce(msg) { if (!liveEl) { return; } liveEl.textContent = ''; setTimeout(function () { liveEl.textContent = msg; }, 30); } function mk(tag, attrs, text) { var n = doc.createElement(tag); if (attrs) { Object.keys(attrs).forEach(function (k) { n.setAttribute(k, attrs[k]); }); } if (text !== undefined) { n.textContent = text; } return n; } /* ---------- clipboard & downloads ---------- */ function copyText(text, btn) { function done(ok) { announce(ok ? 'Copied to clipboard' : 'Copy failed. Select the text and copy it manually.'); if (btn) { var old = btn.textContent; btn.textContent = ok ? 'Copied' : 'Failed'; setTimeout(function () { btn.textContent = old; }, 1200); } } function fallback() { var ta = mk('textarea', { 'aria-hidden': 'true', tabindex: '-1', 'class': 'copy-ta' }); ta.value = text; doc.body.appendChild(ta); ta.select(); var ok = false; try { ok = doc.execCommand('copy'); } catch (e) { ok = false; } doc.body.removeChild(ta); done(ok); } if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(function () { done(true); }, fallback); } else { fallback(); } } function csvCell(v) { var text = String(v === null || v === undefined ? '' : v); if (/^[ \t\r\n]*[=+\-@]|^[\t\r\n]/.test(text)) { text = "'" + text; } return '"' + text.replace(/"/g, '""') + '"'; } function toCsv(headers, rows) { var out = [headers.map(csvCell).join(',')]; rows.forEach(function (r) { out.push(r.map(csvCell).join(',')); }); return out.join('\r\n') + '\r\n'; } function safeName(n) { return String(n).replace(/[^A-Za-z0-9._-]/g, '_').slice(0, 120); } function saveFile(name, mime, text) { var blob = new Blob([text], { type: mime }); var url = URL.createObjectURL(blob); var a = mk('a', { href: url, download: name }); doc.body.appendChild(a); a.click(); setTimeout(function () { URL.revokeObjectURL(url); doc.body.removeChild(a); }, 1000); } /* ---------- scope (one toolbar + its lists) ---------- */ function Scope(el, index) { var self = this; this.el = el; this.prefix = index === 0 ? '' : 'c.'; this.dlName = safeName(el.getAttribute('data-dlname') || 'utcm-report'); this.dlMode = el.getAttribute('data-download') || 'dom'; this.islandId = el.getAttribute('data-island') || ''; this.groupable = el.getAttribute('data-groupable') === '1'; this.presorted = el.getAttribute('data-presorted') === '1'; this.defaults = { q: '', st: '', wl: '', ty: '', so: el.getAttribute('data-sort') || 'name', di: 'asc', sz: 50, gr: this.groupable, co: el.classList.contains('changed-only'), hc: [] }; this.state = JSON.parse(JSON.stringify(this.defaults)); this.tokens = []; this.dirty = true; this.first = true; this.items = qa('tbody.item', el).map(function (tb) { return { el: tb, list: null, match: true, raw: null, dn: tb.getAttribute('data-name') || '', name: (tb.getAttribute('data-name') || '').toLowerCase(), id: tb.getAttribute('data-id') || '', type: tb.getAttribute('data-type') || '', workload: tb.getAttribute('data-workload') || '', status: tb.getAttribute('data-status') || '', changes: parseInt(tb.getAttribute('data-changes') || '0', 10), rows: (tb.getAttribute('data-rows') || '').split(',').filter(Boolean).map(Number) }; }); this.items.forEach(function (it) { it.search = (it.id + ' ' + it.dn + ' ' + it.type + ' ' + it.workload).toLowerCase(); }); this.lists = qa('[data-list]', el).map(function (l) { return { el: l, table: q1('table', l), group: l.getAttribute('data-group') || '', flat: l.hasAttribute('data-flat'), pager: q1('[data-pager]', l), page: 1, items: [], matchCount: 0 }; }); this.countEl = q1('[data-count]', el); this.emptyEl = q1('[data-empty]', el); this.bind(); el.addEventListener('keydown', function (e) { if (e.key === 'Escape') { var m = q1('.menu[open]', el); if (m) { m.removeAttribute('open'); } } }); self.syncControls(); } function ord(a, b) { return a < b ? -1 : (a > b ? 1 : 0); } Scope.prototype.cmp = function (a, b) { var k = this.state.so, d = this.state.di === 'desc' ? -1 : 1, r = 0; if (k === 'status') { r = (STATUS_RANK[a.status] || 0) - (STATUS_RANK[b.status] || 0); } else if (k === 'changes') { r = a.changes - b.changes; } else if (k === 'type') { r = collator.compare(a.type, b.type); } else if (k === 'workload') { r = collator.compare(a.workload, b.workload); } else { r = ord(a.name, b.name); } // Name then id use ordinal comparison, the same order the server uses for presorted markup if (r === 0) { r = ord(a.name, b.name); } if (r === 0) { r = ord(a.id, b.id); } return r * d; }; Scope.prototype.rawText = function (it) { if (it.raw === null) { it.raw = qa('.raw', it.el).map(function (n) { return n.textContent; }).join('\n').toLowerCase(); } return it.raw; }; Scope.prototype.matches = function (it) { var s = this.state; if (s.st && it.status !== s.st) { return false; } if (s.wl && it.workload !== s.wl) { return false; } if (s.ty && it.type !== s.ty) { return false; } for (var i = 0; i < this.tokens.length; i++) { var t = this.tokens[i]; if (it.search.indexOf(t) === -1 && this.rawText(it).indexOf(t) === -1) { return false; } } return true; }; Scope.prototype.listFor = function (it) { var grouped = this.groupable && this.state.gr; for (var i = 0; i < this.lists.length; i++) { var l = this.lists[i]; if (grouped ? (!l.flat && l.group === it.workload) : l.flat) { return l; } } return null; }; Scope.prototype.render = function (userAction) { var self = this, s = this.state; this.tokens = s.q.toLowerCase().split(/\s+/).filter(Boolean); var grouped = this.groupable && s.gr; // Server-sorted markup already matches the default view: skip the first sort and DOM move. var useDom = this.first && this.presorted && s.so === this.defaults.so && s.di === 'asc' && (!this.groupable || s.gr === this.defaults.gr); qa('[data-grouped-wrap]', this.el).forEach(function (n) { n.hidden = !grouped; }); qa('[data-flat-wrap]', this.el).forEach(function (n) { n.hidden = grouped; }); if (this.dirty) { this.sorted = useDom ? this.items.slice() : this.items.slice().sort(this.cmp.bind(this)); } this.lists.forEach(function (l) { l.items = []; }); this.sorted.forEach(function (it) { var l = self.listFor(it); it.list = l; if (l) { l.items.push(it); } }); if (this.dirty) { if (!useDom) { this.lists.forEach(function (l) { var frag = doc.createDocumentFragment(); l.items.forEach(function (it) { frag.appendChild(it.el); }); l.table.appendChild(frag); }); } this.dirty = false; } var total = this.items.length, matched = 0, flatShown = ''; this.lists.forEach(function (l) { var ms = []; l.items.forEach(function (it) { it.match = self.matches(it); if (it.match) { ms.push(it); } }); l.matchCount = ms.length; var pages = Math.max(1, Math.ceil(ms.length / s.sz)); if (l.page > pages) { l.page = pages; } var start = (l.page - 1) * s.sz, end = start + s.sz, idx = 0; l.items.forEach(function (it) { if (!it.match) { it.el.classList.add('filtered-out'); it.el.classList.remove('pg-hidden'); return; } it.el.classList.remove('filtered-out'); var hidden = idx < start || idx >= end; it.el.classList.toggle('pg-hidden', hidden); if (!hidden && !it.btn) { addCopyButtons(it.el); it.btn = true; } idx++; }); matched += ms.length; var g = l.el.closest('details.wl-group'); if (g) { var gc = q1('.gc', g); if (gc) { gc.textContent = '(' + (ms.length === l.items.length ? l.items.length : ms.length + ' of ' + l.items.length) + ')'; } g.hidden = l.items.length === 0 || (ms.length === 0 && (self.tokens.length > 0 || s.st || s.wl || s.ty)); } if (l.flat && ms.length > s.sz && !(self.groupable && s.gr)) { flatShown = ' (showing ' + (start + 1) + '\u2013' + Math.min(end, ms.length) + ')'; } self.buildPager(l, pages); }); if (this.countEl) { this.countEl.textContent = (matched === total ? total + ' items' : matched + ' of ' + total + ' items match') + flatShown; } if (this.emptyEl) { this.emptyEl.hidden = matched > 0 || total === 0; } this.syncControls(); if (userAction && this.countEl) { announce(this.countEl.textContent); } this.first = false; }; Scope.prototype.buildPager = function (l, pages) { var self = this, p = l.pager; if (!p) { return; } while (p.firstChild) { p.removeChild(p.firstChild); } if (l.matchCount <= SIZES[0]) { return; } var nav = mk('nav', { 'class': 'pager', 'aria-label': 'Pagination' }); var prev = mk('button', { type: 'button', 'data-pg': 'prev' }, 'Previous'); var next = mk('button', { type: 'button', 'data-pg': 'next' }, 'Next'); prev.disabled = l.page <= 1; next.disabled = l.page >= pages; nav.appendChild(prev); nav.appendChild(mk('span', { 'class': 'pg' }, 'Page ' + l.page + ' of ' + pages)); nav.appendChild(next); var lab = mk('label', { 'class': 'fld chk' }, 'Rows per page '); var sel = mk('select', { 'data-pgsize': '1', 'aria-label': 'Rows per page' }); SIZES.forEach(function (n) { var o = mk('option', { value: String(n) }, String(n)); if (n === self.state.sz) { o.selected = true; } sel.appendChild(o); }); lab.appendChild(sel); nav.appendChild(lab); p.appendChild(nav); p._list = l; }; Scope.prototype.syncControls = function () { var s = this.state, el = this.el; var qEl = q1('[data-f=q]', el); if (qEl && qEl.value !== s.q) { qEl.value = s.q; } ['wl:workload', 'ty:type', 'so:sort'].forEach(function (pair) { var kv = pair.split(':'), sel = q1('[data-f=' + kv[1] + ']', el); if (sel) { sel.value = s[kv[0]]; } }); qa('.chip', el).forEach(function (c) { c.setAttribute('aria-pressed', c.getAttribute('data-status') === s.st && s.st ? 'true' : 'false'); }); var co = q1('[data-f=changed]', el); if (co) { co.checked = s.co; } var gr = q1('[data-f=group]', el); if (gr) { gr.checked = s.gr; } el.classList.toggle('changed-only', !!s.co); COLS.forEach(function (c) { el.classList.toggle('hc-' + c, s.hc.indexOf(c) !== -1); }); qa('[data-col-toggle]', el).forEach(function (cb) { cb.checked = s.hc.indexOf(cb.getAttribute('data-col-toggle')) === -1; }); qa('thead th', el).forEach(function (th) { var b = q1('.sortbtn', th); if (b) { th.setAttribute('aria-sort', b.getAttribute('data-sort') === s.so ? (s.di === 'desc' ? 'descending' : 'ascending') : 'none'); } }); }; Scope.prototype.change = function (mutate, userAction) { mutate(this.state); this.lists.forEach(function (l) { l.page = 1; }); this.render(userAction !== false); writeHash(); }; Scope.prototype.download = function (kind) { var filtered = /filtered$/.test(kind), fmt = kind.split('-')[0]; var items = this.items.filter(function (it) { return !filtered || it.match; }); var base = this.dlName + (filtered ? '-filtered' : ''); if (this.dlMode === 'island') { var data = JSON.parse((doc.getElementById(this.islandId) || { textContent: '{"rows":[]}' }).textContent); var idx = []; items.forEach(function (it) { it.rows.forEach(function (r) { idx.push(r); }); }); idx.sort(function (a, b) { return a - b; }); var rows = idx.map(function (i) { return data.rows[i]; }).filter(Boolean); if (fmt === 'csv') { saveFile(base + '.csv', 'text/csv;charset=utf-8', toCsv(['Id', 'DisplayName', 'Type', 'DriftType', 'NormalizedData'], rows.map(function (r) { return [r.Id, r.DisplayName, r.Type, r.DriftType, r.NormalizedData]; }))); } else { saveFile(base + '.json', 'application/json', JSON.stringify(rows, null, 2)); } return; } var recs = items.map(function (it) { var pre = q1('.raw', it.el), text = pre ? pre.textContent : '', parsed = null, compact = text; try { parsed = JSON.parse(text); compact = JSON.stringify(parsed); } catch (e) { parsed = text; } return { it: it, parsed: parsed, compact: compact }; }); if (fmt === 'csv') { saveFile(base + '.csv', 'text/csv;charset=utf-8', toCsv(['Id', 'DisplayName', 'Type', 'Workload', 'Data'], recs.map(function (r) { return [r.it.id, r.it.dn, r.it.type, r.it.workload, r.compact]; }))); } else { saveFile(base + '.json', 'application/json', JSON.stringify(recs.map(function (r) { return { id: r.it.id, displayName: r.it.dn, type: r.it.type, workload: r.it.workload, data: r.parsed }; }), null, 2)); } }; Scope.prototype.bind = function () { var self = this, el = this.el, timer = null; el.addEventListener('input', function (e) { if (e.target.matches('[data-f=q]')) { clearTimeout(timer); var v = e.target.value.slice(0, 200); timer = setTimeout(function () { self.change(function (s) { s.q = v; }); }, 150); } }); el.addEventListener('change', function (e) { var t = e.target, f = t.getAttribute('data-f'); if (f === 'workload') { self.change(function (s) { s.wl = t.value; }); } else if (f === 'type') { self.change(function (s) { s.ty = t.value; }); } else if (f === 'sort') { self.change(function (s) { s.so = t.value; s.di = 'asc'; self.dirty = true; }); } else if (f === 'changed') { self.change(function (s) { s.co = t.checked; }); } else if (f === 'group') { self.change(function (s) { s.gr = t.checked; self.dirty = true; }); } else if (t.hasAttribute('data-col-toggle')) { var c = t.getAttribute('data-col-toggle'); self.change(function (s) { s.hc = s.hc.filter(function (x) { return x !== c; }); if (!t.checked) { s.hc.push(c); } }, false); } else if (t.hasAttribute('data-pgsize')) { self.change(function (s) { s.sz = parseInt(t.value, 10) || 50; }); } }); el.addEventListener('click', function (e) { var t = e.target.closest('button'); if (!t || !el.contains(t)) { return; } if (t.classList.contains('chip')) { var st = t.getAttribute('data-status'); self.change(function (s) { s.st = s.st === st ? '' : st; }); } else if (t.classList.contains('sortbtn')) { var key = t.getAttribute('data-sort'); self.change(function (s) { if (s.so === key) { s.di = s.di === 'asc' ? 'desc' : 'asc'; } else { s.so = key; s.di = 'asc'; } self.dirty = true; }); } else if (t.hasAttribute('data-pg')) { var pager = t.closest('[data-pager]'), l = pager && pager._list; if (l) { l.page += t.getAttribute('data-pg') === 'next' ? 1 : -1; self.render(true); } } else if (t.classList.contains('copybtn')) { var holder = t.closest('.cp'), v = holder && q1('.v', holder); if (v) { copyText(v.textContent, t); } } else if (t.getAttribute('data-act') === 'clear') { self.change(function (s) { var d = self.defaults; s.q = d.q; s.st = d.st; s.wl = d.wl; s.ty = d.ty; }); } else if (t.getAttribute('data-act') === 'copylink') { copyText(location.href, t); } else if (t.getAttribute('data-act') === 'expand' || t.getAttribute('data-act') === 'collapse') { var open = t.getAttribute('data-act') === 'expand'; self.items.forEach(function (it) { if (!it.match || it.el.classList.contains('pg-hidden')) { return; } qa('tr.detail details', it.el).forEach(function (d) { if (d.parentElement.tagName === 'TD') { d.open = open; } }); }); } else if (t.getAttribute('data-act') === 'groups-open' || t.getAttribute('data-act') === 'groups-close') { var o = t.getAttribute('data-act') === 'groups-open'; qa('details.wl-group', el).forEach(function (g) { g.open = o; }); } else if (t.hasAttribute('data-dl')) { self.download(t.getAttribute('data-dl')); var menu = t.closest('.menu'); if (menu) { menu.removeAttribute('open'); } } }); }; /* ---------- URL hash state ---------- */ function parseHash() { var raw = location.hash.replace(/^#/, ''), i = raw.indexOf('?'); var section = i === -1 ? raw : raw.slice(0, i), qs = i === -1 ? '' : raw.slice(i + 1), params = {}; try { section = decodeURIComponent(section); } catch (e) { section = ''; } qs.split('&').forEach(function (pair) { if (!pair) { return; } var kv = pair.split('='); try { params[decodeURIComponent(kv[0])] = decodeURIComponent((kv[1] || '').replace(/\+/g, ' ')); } catch (e) { /* ignore malformed */ } }); return { section: section, params: params }; } function optionValues(scope, name) { var sel = q1('[data-f=' + name + ']', scope.el); return sel ? qa('option', sel).map(function (o) { return o.value; }) : []; } function applyParams(scope, params) { var d = scope.defaults, s = scope.state, p = scope.prefix; function get(k) { return Object.prototype.hasOwnProperty.call(params, p + k) ? params[p + k] : null; } var v; s.q = (v = get('q')) !== null ? v.slice(0, 200) : d.q; var statuses = qa('.chip', scope.el).map(function (c) { return c.getAttribute('data-status'); }); s.st = (v = get('st')) !== null && statuses.indexOf(v) !== -1 ? v : d.st; s.wl = (v = get('wl')) !== null && optionValues(scope, 'workload').indexOf(v) !== -1 ? v : d.wl; s.ty = (v = get('ty')) !== null && optionValues(scope, 'type').indexOf(v) !== -1 ? v : d.ty; var sorts = optionValues(scope, 'sort'); s.so = (v = get('so')) !== null && SORT_KEYS.indexOf(v) !== -1 && sorts.indexOf(v) !== -1 ? v : d.so; s.di = (v = get('di')) === 'desc' ? 'desc' : d.di; s.sz = (v = parseInt(get('sz'), 10)) && SIZES.indexOf(v) !== -1 ? v : d.sz; s.gr = scope.groupable && (v = get('gr')) !== null ? v === '1' : d.gr; s.co = (v = get('co')) !== null ? v === '1' : d.co; var available = qa('[data-col-toggle]', scope.el).map(function (c) { return c.getAttribute('data-col-toggle'); }); s.hc = (v = get('hc')) !== null ? v.split(',').filter(function (c) { return COLS.indexOf(c) !== -1 && available.indexOf(c) !== -1; }) : d.hc.slice(); scope.dirty = true; } function serialize() { var parts = []; scopes.forEach(function (sc) { var d = sc.defaults, s = sc.state; function add(k, v) { parts.push(encodeURIComponent(sc.prefix + k) + '=' + encodeURIComponent(v)); } if (s.q !== d.q) { add('q', s.q); } if (s.st !== d.st) { add('st', s.st); } if (s.wl !== d.wl) { add('wl', s.wl); } if (s.ty !== d.ty) { add('ty', s.ty); } if (s.so !== d.so) { add('so', s.so); } if (s.di !== d.di) { add('di', s.di); } if (s.sz !== d.sz) { add('sz', s.sz); } if (s.gr !== d.gr) { add('gr', s.gr ? '1' : '0'); } if (s.co !== d.co) { add('co', s.co ? '1' : '0'); } if (s.hc.join(',') !== d.hc.join(',')) { add('hc', s.hc.join(',')); } }); return parts.join('&'); } function writeHash() { var qs = serialize(), h = (currentSection || qs) ? '#' + currentSection + (qs ? '?' + qs : '') : ''; try { history.replaceState(null, '', location.pathname + location.search + h); } catch (e) { /* ignore */ } } function scrollToSection(section) { var t = section && doc.getElementById(section); if (t) { t.scrollIntoView(); } } /* ---------- init ---------- */ function addCopyButtons(root) { qa('.cp', root).forEach(function (s) { if (s.querySelector('.copybtn')) { return; } s.appendChild(mk('button', { type: 'button', 'class': 'copybtn', 'aria-label': s.classList.contains('cp-id') ? 'Copy ID' : 'Copy value' }, 'Copy')); }); } qa('[data-scope]').forEach(function (el, i) { scopes.push(new Scope(el, i)); }); var initial = parseHash(); currentSection = doc.getElementById(initial.section) ? initial.section : ''; scopes.forEach(function (sc) { applyParams(sc, initial.params); sc.render(false); }); if (currentSection) { scrollToSection(currentSection); } window.addEventListener('hashchange', function () { var h = parseHash(); currentSection = doc.getElementById(h.section) ? h.section : ''; if (Object.keys(h.params).length) { scopes.forEach(function (sc) { applyParams(sc, h.params); sc.render(false); }); } scrollToSection(currentSection); writeHash(); }); qa('a[data-nav]').forEach(function (a) { a.addEventListener('click', function () { currentSection = (a.getAttribute('href') || '').replace(/^#/, ''); setTimeout(writeHash, 0); }); }); var opened = []; window.addEventListener('beforeprint', function () { opened = []; qa('details.wl-group').forEach(function (g) { if (!g.open) { g.open = true; opened.push(g); } }); }); window.addEventListener('afterprint', function () { opened.forEach(function (g) { g.open = false; }); opened = []; }); })(); '@ return ($js -replace "`r`n", "`n").Trim() } |