diff --git a/web/routes/dashboard.py b/web/routes/dashboard.py index 8322869..828aa04 100644 --- a/web/routes/dashboard.py +++ b/web/routes/dashboard.py @@ -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 = '
' + content.replace('<', '<') + ''
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']})
diff --git a/web/static/js/app.js b/web/static/js/app.js
index cf47366..7363d71 100644
--- a/web/static/js/app.js
+++ b/web/static/js/app.js
@@ -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 = '↻ Reloading...';
+ btn.disabled = true;
+ btn.style.color = 'var(--accent)';
+
+ postJSON('/api/modules/reload', {}).then(function(data) {
+ var total = data.total || 0;
+ btn.innerHTML = '✓ ' + 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 = '✗ 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);
diff --git a/web/templates/base.html b/web/templates/base.html
index ed07364..47fc59d 100644
--- a/web/templates/base.html
+++ b/web/templates/base.html
@@ -64,6 +64,13 @@