Skip to content

Commit

Permalink
zaproxy#244 Progressively enhance alerts table with filter controls
Browse files Browse the repository at this point in the history
  • Loading branch information
rezen committed Feb 25, 2021
1 parent fcdb224 commit d79ed0a
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 18 deletions.
39 changes: 21 additions & 18 deletions site/layouts/alert/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,27 @@ <h1 class="text--white">{{ .Title }}</h1>
<div class="wrapper py-70">
{{ .Content }}
<div class="flex latest-versions">
<table>
<TR>
<TH>Id</TH>
<TH>Alert</TH>
<TH>Status</TH>
<TH>Risk</TH>
<TH>Type</TH>
</TR>
{{ range (.Pages.ByParam "alertindex") }}
<tr>
<td><a href="{{ .Permalink }}">{{ .Params.alertid }}</a></td>
<td><a href="{{ .Permalink }}">{{ .Title }}</a></td>
<td>{{ .Params.status }}</td>
<td>{{ .Params.risk }}</td>
<td>{{ .Params.alerttype }}</td>
</tr>
</li>
{{ end }}
<table data-sort-filter>
<thead>
<tr>
<th>Id</th>
<th>Alert</th>
<th data-suggest>Status</th>
<th data-suggest>Risk</th>
<th data-suggest>Type</th>
</tr>
</thead>
<tbody>
{{ range (.Pages.ByParam "alertindex") }}
<tr>
<td><a href="{{ .Permalink }}">{{ .Params.alertid }}</a></td>
<td><a href="{{ .Permalink }}">{{ .Title }}</a></td>
<td>{{ .Params.status }}</td>
<td>{{ .Params.risk }}</td>
<td>{{ .Params.alerttype }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
Expand Down
91 changes: 91 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ new BadgerAccordion(".js-badger-accordion", {
openHeadersOnLoad: [0]
});

function removeAllChildNodes(parent) {
while (parent.firstChild) {
parent.removeChild(parent.firstChild);
}
}

document.addEventListener("DOMContentLoaded", function() {
// Basic attempt to obfuscate emails
Array.from(document.querySelectorAll("[data-mail]")).map(function(el) {
Expand All @@ -18,6 +24,91 @@ document.addEventListener("DOMContentLoaded", function() {
}, {once: true});
});

// Make tables filterable
Array.from(document.querySelectorAll("[data-sort-filter]")).map(function(el) {
const widget = {
options: {},
filters: {}
};

// Checks if row matches against filter
function isFilterMatch(row) {
for (let index in widget.filters) {
const filter = widget.filters[index].toLowerCase();
const rowValue = row.columns[index].toLowerCase();
if (rowValue.indexOf(filter) === -1) {
return false;
}
}
return true;
}

// Create datalist that input can use for suggetions
function setupDatalist(el, label) {
widget.options[idx] = document.createElement('datalist');
widget.options[idx].setAttribute('id', 'opts_for_' + label);
widget.options[idx]._options = []
el.appendChild(widget.options[idx]);
}

// Add input for filtering
function addInput(el, label) {
const input = document.createElement('input');
input.addEventListener("change", function(e) {
widget.filters[idx] = e.target.value;
removeAllChildNodes(tbody);
rows.filter(isFilterMatch).map(r => {
tbody.appendChild(r.el)
});
});
input.setAttribute('style', 'width:100%;display:block')
input.setAttribute('type', 'text');
input.setAttribute('name', 'filter_' + label);
input.setAttribute('list', 'opts_for_' + label);
el.appendChild(input);
}
const tbody = el.querySelector('tbody');
const headings = Array.from(el.querySelectorAll('thead th')).map((el, idx) => {
const isSuggested = el.getAttribute("data-suggest") !== null;
const label = el.innerText.toLowerCase();
el.appendChild(document.createElement('br'));
addInput(el, label);

if (isSuggested) {
setupDatalist(el, label);
}
return {idx, isSuggested, label};
})

const rows = Array.from(el.querySelectorAll('tbody tr')).map(tr => {
const columns = Array.from(tr.querySelectorAll('td')).map((c, idx) => {
// For columns that match the index of the `data-suggest` headers
// ... add the text value to options
if (widget.options[idx]) {
widget.options[idx]._options.push(c.innerText)
}
return c.innerText;
});
return {
el: tr, // Needed for writing to dom
columns, // Needed for filtered
};
});

// Go through options elements and populate lists with column aggregates
// gathered in previous loop
Object.entries(widget.options).map(pair => {
const [idx, el] = pair;
const opts = [...new Set(el._options)];
opts.sort();
opts.map(o => {
const ol = document.createElement('option');
ol.innerText = o;
return ol;
}).map(ol => el.appendChild(ol));
});
});

function clearFilter(menu) {
menu.classList.remove("is-filtering");
Array.from(menu.getElementsByTagName('li')).map(function(el) {
Expand Down

0 comments on commit d79ed0a

Please sign in to comment.