Add Refresh Modules button to sidebar for hot-reloading modules

- Sidebar button at bottom re-scans modules/ directory on click
- POST /api/modules/reload endpoint returns updated counts and module list
- Button shows success/failure feedback, auto-reloads category pages
- Enables hot-dropping new modules without restarting the server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
DigiJ 2026-03-02 21:12:56 -08:00
parent a3ec1a2556
commit 150f58f57a
3 changed files with 70 additions and 1 deletions

View File

@ -6,7 +6,7 @@ import socket
import time
from datetime import datetime
from pathlib import Path
from flask import Blueprint, render_template, current_app
from flask import Blueprint, render_template, current_app, jsonify
from markupsafe import Markup
from web.auth import login_required
@ -116,3 +116,27 @@ def manual_windows():
except ImportError:
html = '<pre>' + content.replace('<', '&lt;') + '</pre>'
return render_template('manual.html', manual_html=Markup(html))
@dashboard_bp.route('/api/modules/reload', methods=['POST'])
@login_required
def reload_modules():
"""Re-scan modules directory and return updated counts + module list."""
from core.menu import MainMenu
menu = MainMenu()
menu.load_modules()
counts = {}
modules = []
for name, info in menu.modules.items():
cat = info.category
counts[cat] = counts.get(cat, 0) + 1
modules.append({
'name': name,
'category': cat,
'description': info.description,
'version': info.version,
})
counts['total'] = len(menu.modules)
return jsonify({'counts': counts, 'modules': modules, 'total': counts['total']})

View File

@ -41,6 +41,44 @@ function renderOutput(elementId, text) {
if (el) el.textContent = text;
}
/* ── Refresh Modules ─────────────────────────────────────────── */
function reloadModules() {
var btn = document.getElementById('btn-reload-modules');
if (!btn) return;
var origText = btn.innerHTML;
btn.innerHTML = '&#x21BB; Reloading...';
btn.disabled = true;
btn.style.color = 'var(--accent)';
postJSON('/api/modules/reload', {}).then(function(data) {
var total = data.total || 0;
btn.innerHTML = '&#x2713; ' + total + ' modules loaded';
btn.style.color = 'var(--success)';
// If on a category page, reload to reflect changes
var path = window.location.pathname;
var isCategoryPage = /^\/(defense|offense|counter|analyze|osint|simulate)\/?$/.test(path)
|| path === '/';
if (isCategoryPage) {
setTimeout(function() { window.location.reload(); }, 800);
} else {
setTimeout(function() {
btn.innerHTML = origText;
btn.style.color = 'var(--text-secondary)';
btn.disabled = false;
}, 2000);
}
}).catch(function() {
btn.innerHTML = '&#x2717; Reload failed';
btn.style.color = 'var(--danger)';
setTimeout(function() {
btn.innerHTML = origText;
btn.style.color = 'var(--text-secondary)';
btn.disabled = false;
}, 2000);
});
}
function showTab(tabGroup, tabId) {
document.querySelectorAll('[data-tab-group="' + tabGroup + '"].tab').forEach(function(t) {
t.classList.toggle('active', t.dataset.tab === tabId);

View File

@ -64,6 +64,13 @@
<li><a href="{{ url_for('dashboard.manual_windows') }}" class="{% if request.endpoint == 'dashboard.manual_windows' %}active{% endif %}" style="padding-left:1.5rem;font-size:0.85rem">&#x2514; Windows Guide</a></li>
</ul>
</div>
<div style="padding:6px 12px">
<button id="btn-reload-modules" onclick="reloadModules()" class="btn btn-small"
style="width:100%;font-size:0.75rem;padding:5px 0;background:var(--bg-input);border:1px solid var(--border);color:var(--text-secondary)"
title="Re-scan modules/ directory for new or changed modules">
&#x21BB; Refresh Modules
</button>
</div>
<div class="sidebar-footer">
<span class="admin-name">{{ session.get('user', 'admin') }}</span>
<a href="{{ url_for('auth.logout') }}" class="logout-link">Logout</a>