mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-19 22:17:18 +02:00
feat(web): add Re-run button to detail view with pre-filled wizard
Adds a "Re-run" button to the run detail view that opens the wizard pre-populated with the previous run's settings (target, model, votes, chain depth, recon, max agents, mode flags). Server now includes launch params in the detail API response by looking up the persisted job file. Previous findings and reports are preserved (new run ID).
This commit is contained in:
1 parent
78e7d27e2f
commit
33fd6ca508
3 files changed
+50
-1
No files matched your search
@@ -1148,6 +1148,41 @@ $('#btnDeleteRun').addEventListener('click', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
$('#btnRerun')?.addEventListener('click', () => {
|
||||
clearInterval(state.detailPoll);
|
||||
const target = $('#detailTarget').textContent || '';
|
||||
const sub = $('#detailTargetSub').textContent || '';
|
||||
const launch = state.detailLaunch;
|
||||
switchView('wizard');
|
||||
goToStep(0);
|
||||
if (launch) {
|
||||
if (launch.target) $('#fieldTarget').value = launch.target;
|
||||
if (launch.name) $('#fieldName').value = launch.name + ' (re-run)';
|
||||
if (launch.repo) $('#fieldRepo').value = launch.repo;
|
||||
if (launch.mode) { const sel = $('#fieldMode'); if (sel) sel.value = launch.mode; }
|
||||
if (launch.votes) $('#fieldVotes').value = launch.votes;
|
||||
if (launch.chainDepth !== undefined) $('#fieldChain').value = launch.chainDepth;
|
||||
if (launch.recon) $('#fieldRecon').value = launch.recon;
|
||||
if (launch.maxAgents) $('#fieldMaxAgents').value = launch.maxAgents;
|
||||
if (launch.quick && $('#fieldQuick')) $('#fieldQuick').checked = true;
|
||||
if (launch.research && $('#fieldResearch')) $('#fieldResearch').checked = true;
|
||||
if (launch.subscription) state.authMode = 'subscription';
|
||||
if (launch.mcp && $('#fieldMcp')) $('#fieldMcp').checked = launch.mcp;
|
||||
if (launch.models?.length) {
|
||||
const parts = launch.models[0].split(':');
|
||||
if (parts.length >= 2) {
|
||||
$('#fieldProvider').value = parts[0];
|
||||
$('#fieldProvider').dispatchEvent(new Event('change'));
|
||||
setTimeout(() => { $('#fieldModelSelect').value = parts.slice(1).join(':'); }, 100);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$('#fieldTarget').value = sub || target;
|
||||
$('#fieldName').value = (target || '') + ' (re-run)';
|
||||
}
|
||||
toast('Settings loaded from previous run. Adjust and launch.', 'ok', 4000);
|
||||
});
|
||||
|
||||
$('#btnDetailBack').addEventListener('click', () => { clearInterval(state.detailPoll); switchView('wizard'); });
|
||||
$('#btnNewEngagement')?.addEventListener('click', () => { leaveLiveJob(); clearInterval(state.detailPoll); switchView('wizard'); });
|
||||
if ($('#btnNewPentest2')) $('#btnNewPentest2').addEventListener('click', () => { leaveLiveJob(); clearInterval(state.detailPoll); switchView('wizard'); });
|
||||
@@ -1758,6 +1793,7 @@ async function loadDetail(id) {
|
||||
$('#detailState').textContent = detail.status?.state || 'unknown';
|
||||
state.detailFindings = detail.findings;
|
||||
state.detailPocs = detail.pocs || [];
|
||||
state.detailLaunch = detail.launch || null;
|
||||
// Regression vs the previous run of this target — new / recurring badges and a
|
||||
// "fixed since" banner. Best-effort: it never blocks the findings table.
|
||||
state.detailRegression = null;
|
||||
|
||||
@@ -741,6 +741,7 @@
|
||||
<button class="btn" id="btnBuildReport" title="Regenerate this run's report from its findings">Generate report</button>
|
||||
<button class="btn" id="btnGraphRecompute" title="Recompute the attack graph with the CLI (neurosploit graph)">↻ Graph</button>
|
||||
<button class="btn" id="btnRetest" title="Re-run every finding's recorded proof (neurosploit poc) and see what still reproduces">⟳ Re-test</button>
|
||||
<button class="btn btn-accent" id="btnRerun" title="Launch a new run against this target with the same (or tweaked) settings">▶ Re-run</button>
|
||||
<a class="btn" id="detailOpenAudit" target="_blank" hidden title="Every action this run took, hash-chained">Audit trail</a>
|
||||
<button class="btn btn-danger" id="btnDeleteRun" title="Permanently delete this session and its report">🗑 Delete</button>
|
||||
<button class="btn" id="btnDetailBack">← Back</button>
|
||||
|
||||
+13
-1
@@ -791,7 +791,19 @@ async function runDetail(id) {
|
||||
const liveRunIds = new Set([...jobs.values()].filter((j) => !j.done && j.runId).map((j) => j.runId));
|
||||
if (!liveRunIds.has(id)) status.state = 'interrupted';
|
||||
}
|
||||
return { id, name: engagementNames.get(id) || '', meta, status, findings, assets, pocs, extras };
|
||||
let launch = null;
|
||||
const liveJob = [...jobs.values()].find((j) => j.runId === id);
|
||||
if (liveJob?.launch) launch = liveJob.launch;
|
||||
else {
|
||||
try {
|
||||
const jfiles = await fsp.readdir(JOBS_DIR);
|
||||
for (const jf of jfiles) {
|
||||
const rec = JSON.parse(await fsp.readFile(path.join(JOBS_DIR, jf), 'utf8'));
|
||||
if (rec.runId === id && rec.launch) { launch = rec.launch; break; }
|
||||
}
|
||||
} catch { /* no job files */ }
|
||||
}
|
||||
return { id, name: engagementNames.get(id) || '', meta, status, findings, assets, pocs, extras, launch };
|
||||
}
|
||||
|
||||
/// Relative file list under a directory (one level of nesting is enough for
|
||||
|
||||
Reference in new issue
Block a user