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:
parent
a3ec1a2556
commit
150f58f57a
@ -6,7 +6,7 @@ import socket
|
|||||||
import time
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
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 markupsafe import Markup
|
||||||
from web.auth import login_required
|
from web.auth import login_required
|
||||||
|
|
||||||
@ -116,3 +116,27 @@ def manual_windows():
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
html = '<pre>' + content.replace('<', '<') + '</pre>'
|
html = '<pre>' + content.replace('<', '<') + '</pre>'
|
||||||
return render_template('manual.html', manual_html=Markup(html))
|
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']})
|
||||||
|
|||||||
@ -41,6 +41,44 @@ function renderOutput(elementId, text) {
|
|||||||
if (el) el.textContent = 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) {
|
function showTab(tabGroup, tabId) {
|
||||||
document.querySelectorAll('[data-tab-group="' + tabGroup + '"].tab').forEach(function(t) {
|
document.querySelectorAll('[data-tab-group="' + tabGroup + '"].tab').forEach(function(t) {
|
||||||
t.classList.toggle('active', t.dataset.tab === tabId);
|
t.classList.toggle('active', t.dataset.tab === tabId);
|
||||||
|
|||||||
@ -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">└ Windows Guide</a></li>
|
<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">└ Windows Guide</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
||||||
|
↻ Refresh Modules
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<span class="admin-name">{{ session.get('user', 'admin') }}</span>
|
<span class="admin-name">{{ session.get('user', 'admin') }}</span>
|
||||||
<a href="{{ url_for('auth.logout') }}" class="logout-link">Logout</a>
|
<a href="{{ url_for('auth.logout') }}" class="logout-link">Logout</a>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user