Improve UX of module explorer

This commit is contained in:
adfoster-r7 2023-02-21 11:56:44 +00:00
parent 292c160abf
commit 1b44973c80
No known key found for this signature in database
GPG Key ID: 3BD4FA3818818F04
4 changed files with 98 additions and 13 deletions

View File

@ -1,19 +1,60 @@
// Handle opening/closing module overview list items
jtd.onReady(function(ready) {
var moduleStructures = document.querySelectorAll('.module-structure');
for (var i = 0; i < moduleStructures.length; i++) {
jtd.addEvent(moduleStructures[i], 'click', function (e) {
var forEach = function (list, callback) {
for (var i = 0; i < list.length; i++) {
callback(list[i])
}
};
// Bind listeners for expand all / collapse all functionality
var bindToggleAll = function (selector, options) {
var isOpen = options.open;
var expandAllButtons = document.querySelectorAll(selector);
forEach(expandAllButtons, function (button) {
jtd.addEvent(button, 'click', function (e) {
var originalTarget = e.target || e.srcElement || e.originalTarget;
if (originalTarget.tagName !== 'A') { return; }
var moduleList = originalTarget.closest('.module-list');
forEach(moduleList.querySelectorAll('.folder > ul'), function (list) {
if (isOpen) {
list.classList.add('open');
} else {
list.classList.remove('open');
}
})
e.preventDefault();
});
});
};
bindToggleAll('.module-list [data-expand-all]', { open: true })
bindToggleAll('.module-list [data-collapse-all]', { open: false })
// Bind listeners for collapsing module navigation items
var moduleStructureElements = document.querySelectorAll('.module-structure');
forEach(moduleStructureElements, function (moduleStructure) {
jtd.addEvent(moduleStructure, 'click', function (e) {
var originalTarget = e.target || e.srcElement || e.originalTarget;
if (originalTarget.tagName !== 'A') { return; }
var parentListItem = originalTarget.closest('li');
if (parentListItem.className.indexOf('folder') === -1) { return; }
var childList = parentListItem.querySelector('ul');
if (childList) {
childList.classList.toggle('open');
}
toggleChildModuleList(parentListItem)
e.preventDefault();
});
})
var toggleChildModuleList = function (parent) {
var list = parent.querySelector('ul');
if (!list) {
return;
}
list.classList.toggle('open');
// Recursively automatically open any nested lists of size 1
if (list.children.length === 1) {
toggleChildModuleList(list.children[0])
}
}
});

View File

@ -6,6 +6,10 @@ require 'pathname'
# Helper class for extracting information related to Metasploit framework's stats
#
class MetasploitStats
def total_module_count
modules.length
end
# @return [Hash<String, Integer>] A map of module type to the amount of modules
def module_counts
module_counts_by_type = modules.group_by { |mod| mod['type'].to_s }.transform_values { |mods| mods.count }.sort_by(&:first).to_h
@ -71,11 +75,27 @@ end
module ModuleFilter
# @param [Array<Hash>] modules The array of Metasploit cache information
# @return [String] The module tree HTML representation of the given modules
def module_tree(modules)
def module_tree(modules, title = 'Modules', show_controls = false)
rendered_children = render_modules(modules)
controls = <<~EOF
<div class="module-controls">
<span><a href="#" data-expand-all>Expand All</a></span>
<span><a href="#" data-collapse-all>Collapse All</a></span>
</div>
EOF
<<~EOF
<ul class="module-structure">#{rendered_children}</ul>
<div class="module-list">
#{show_controls ? controls : ''}
<ul class="module-structure">
<li class="folder"><a href=\"#\"><div class=\"target\">#{title}</div></a>
<ul class="open">
#{rendered_children}
</ul>
</li>
</ul>
</div>
EOF
end
@ -85,7 +105,8 @@ module ModuleFilter
# @return [String] The rendered tree HTML representation of the given modules
def render_modules(modules)
modules.map do |mod|
result = "<li#{render_child_modules?(mod) ? ' class="folder"' : ''}>#{heading_for_mod(mod)}"
classes = render_child_modules?(mod) ? ' class="folder"' : ''
result = "<li#{classes}>#{heading_for_mod(mod)}"
if render_child_modules?(mod)
result += "\n<ul>#{render_modules(mod[:children].sort_by { |mod| "#{render_child_modules?(mod) ? 0 : 1}-#{mod[:name]}" })}</ul>\n"
end
@ -126,7 +147,7 @@ Jekyll::Hooks.register :site, :after_init do |site|
metasploit_stats = MetasploitStats.new
site.config['metasploit_total_module_count'] = metasploit_stats.module_counts.sum { |_type, count| count }
site.config['metasploit_total_module_count'] = metasploit_stats.total_module_count
site.config['metasploit_module_counts'] = metasploit_stats.module_counts
site.config['metasploit_nested_module_counts'] = metasploit_stats.nested_module_counts

View File

@ -45,14 +45,32 @@
width: 90%;
}
.module-controls {
line-height: 0;
border-bottom: 1px solid #ddd;
}
.module-controls a {
line-height: 1;
padding: 0.5rem;
display: inline-block;
}
.module-controls span {
display: inline-block;
}
.module-structure a, .module-structure a:hover {
background-image: none;
}
.module-structure a:hover .target {
.module-structure a .target {
pointer-events: none;
display: inline-block;
text-decoration: none;
}
.module-structure a:hover .target {
background-image: linear-gradient(rgba(114, 83, 237, 0.45) 0%, rgba(114, 83, 237, 0.45) 100%);
background-repeat: repeat-x;
background-position: 0 100%;
@ -70,6 +88,11 @@
border-left: 1px dashed #d1d7de;
}
/* Never allow the top-most files/folders to be collapsed */
.module-structure > li.folder > ul {
display: block;
}
.module-structure li p {
margin: 0;
}

View File

@ -2,7 +2,7 @@
There are currently {{ site.metasploit_total_module_count }} Metasploit modules:
{{ site.metasploit_nested_module_counts | module_tree }}
{{ site.metasploit_nested_module_counts | module_tree: "All Modules", true }}
## Module types