Skip to content

Commit ca90d2a

Browse files
Merge pull request #27 from DMontgomery40/refactor/modular-scanner
Refactor/modular scanner
2 parents 01975ea + b11a9b5 commit ca90d2a

File tree

4 files changed

+264
-33
lines changed

4 files changed

+264
-33
lines changed

src/components/ProgressBar.jsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,25 @@ const ProgressBar = ({ progress, onCancel, scanning }) => {
1212
/>
1313
</div>
1414
<div className="text-sm text-gray-300 mt-2 text-center">
15-
{progress.phase === 'fetching' && progress.total > 0
15+
{progress.phase === 'fetching' && progress.total > 0 && progress.details?.successCount !== undefined
16+
? `Fetching files (${progress.current}/${progress.total}) • ✓${progress.details.successCount}${progress.details.failureCount}`
17+
: progress.phase === 'fetching' && progress.total > 0
1618
? `Fetching files (${progress.current} of ${progress.total})`
19+
: progress.phase === 'analyzing' && progress.total > 0 && progress.details?.successCount !== undefined
20+
? `Analyzing files (${progress.current}/${progress.total}) • ✓${progress.details.successCount}${progress.details.failureCount}`
21+
: progress.phase === 'completed' && progress.details?.summary
22+
? progress.details.summary
1723
: progress.phase === 'analyzing' && progress.details?.currentFile
1824
? `Analyzing: ${progress.details.currentFile} (${progress.current} of ${progress.total})`
1925
: progress.phase === 'complete'
2026
? 'Scan complete!'
2127
: `${progress.phase}: ${progress.current} of ${progress.total}`}
2228
</div>
29+
{progress.phase === 'completed' && progress.details?.failureCount > 0 && (
30+
<div className="text-xs text-yellow-400 mt-1 text-center">
31+
⚠️ {progress.details.failureCount} files couldn't be downloaded - scan may be incomplete
32+
</div>
33+
)}
2334
{onCancel && (
2435
<div className="text-center mt-2">
2536
<button

src/context/ScanContext.jsx

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ export const ScanProvider = ({ children }) => {
134134
});
135135

136136
try {
137-
const results = await scanRepositoryLocally(urlInput);
137+
const results = await scanRepositoryLocally(urlInput, handleProgress);
138138
console.log('Scan results:', results);
139139

140140
if (results.findings && results.summary) {
@@ -166,12 +166,19 @@ export const ScanProvider = ({ children }) => {
166166
}
167167
});
168168

169+
// Include scan statistics in success message
170+
const scanStatsMsg = results.scanStats
171+
? ` • Scanned ${results.scanStats.successCount}/${results.scanStats.totalFiles} files (${results.scanStats.completionRate}%) in ${results.scanStats.duration}s`
172+
: '';
173+
174+
const partialMsg = results.partial ? ' • ⚠️ Some files failed to download' : '';
175+
169176
setSuccessMessage(
170177
`Scan complete! Found ${results.summary.totalIssues} potential vulnerabilities ` +
171178
`(${results.summary.criticalIssues} critical, ` +
172179
`${results.summary.highIssues} high, ` +
173180
`${results.summary.mediumIssues} medium, ` +
174-
`${results.summary.lowIssues} low)`
181+
`${results.summary.lowIssues} low)${scanStatsMsg}${partialMsg}`
175182
);
176183

177184
setUsedCache(results.fromCache || false);
@@ -203,11 +210,16 @@ export const ScanProvider = ({ children }) => {
203210
setError(null);
204211
setScanResults(null);
205212
setSuccessMessage('');
213+
const startTime = Date.now();
206214
setProgress({
207215
phase: 'fetching',
208216
current: 0,
209217
total: 0,
210-
details: { url }
218+
details: {
219+
url,
220+
status: 'Fetching webpage content...',
221+
startTime
222+
}
211223
});
212224
setFirmwareMessage('');
213225

@@ -259,11 +271,53 @@ export const ScanProvider = ({ children }) => {
259271
}
260272
});
261273

274+
// Calculate scan duration
275+
const endTime = Date.now();
276+
const duration = Math.round((endTime - startTime) / 1000 * 100) / 100;
277+
278+
// Count scanned items (HTML + scripts)
279+
const scriptCount = data.scriptsScanned || 0;
280+
const totalScanned = scriptCount + 1; // +1 for HTML page
281+
282+
// Send completion progress
283+
setProgress({
284+
phase: 'completed',
285+
current: totalScanned,
286+
total: totalScanned,
287+
details: {
288+
duration: duration,
289+
successCount: totalScanned,
290+
failureCount: 0,
291+
completionRate: 100,
292+
totalAttempted: totalScanned,
293+
summary: `Scanned ${totalScanned} items (HTML + ${scriptCount} scripts) in ${duration}s`
294+
}
295+
});
296+
262297
setSuccessMessage(
263-
`Website scan complete! Found ${summary.totalIssues || 0} potential vulnerabilities.`
298+
`Website scan complete! Found ${summary.totalIssues || 0} potential vulnerabilities • ` +
299+
`Scanned ${totalScanned} items in ${duration}s`
264300
);
265301
} else {
266-
setSuccessMessage('Website scan completed, but no vulnerabilities reported.');
302+
// Calculate scan duration even for no results
303+
const endTime = Date.now();
304+
const duration = Math.round((endTime - startTime) / 1000 * 100) / 100;
305+
306+
setProgress({
307+
phase: 'completed',
308+
current: 1,
309+
total: 1,
310+
details: {
311+
duration: duration,
312+
successCount: 1,
313+
failureCount: 0,
314+
completionRate: 100,
315+
totalAttempted: 1,
316+
summary: `Scanned webpage in ${duration}s`
317+
}
318+
});
319+
320+
setSuccessMessage(`Website scan completed in ${duration}s, but no vulnerabilities reported.`);
267321
}
268322
} catch (err) {
269323
console.error('Website scan error:', err);

src/lib/RepositoryCrawler.js

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -298,13 +298,33 @@ export class RepositoryCrawler {
298298
// Get file contents with semaphore-controlled concurrency
299299
const blobFiles = filteredTree.filter(item => item.type === 'blob');
300300
const filesWithContent = [];
301-
let processedFiles = 0;
302301
const totalFiles = blobFiles.length;
303302

304303
this.clearErrors(); // Clear any previous errors
304+
305+
// Track timing and statistics
306+
const startTime = Date.now();
307+
let successCount = 0;
308+
let failureCount = 0;
309+
310+
// Initialize progress tracking
311+
if (onProgress) {
312+
onProgress({
313+
phase: 'fetching',
314+
current: 0,
315+
total: totalFiles,
316+
details: {
317+
status: 'Starting file downloads...',
318+
startTime: startTime
319+
}
320+
});
321+
}
322+
323+
// Track progress with atomic counter to avoid race conditions
324+
let completedCount = 0;
305325

306326
// Use semaphore to control concurrency
307-
const filePromises = blobFiles.map(file =>
327+
const filePromises = blobFiles.map((file, index) =>
308328
this.semaphore.execute(async () => {
309329
try {
310330
// Use raw content URL for better performance
@@ -319,17 +339,40 @@ export class RepositoryCrawler {
319339
statusText: response.statusText,
320340
filePath: file.path
321341
});
342+
343+
// Update progress and failure count atomically
344+
completedCount++;
345+
failureCount++;
346+
if (onProgress) {
347+
onProgress({
348+
phase: 'fetching',
349+
current: completedCount,
350+
total: totalFiles,
351+
details: {
352+
currentFile: file.path,
353+
successCount: successCount,
354+
failureCount: failureCount
355+
}
356+
});
357+
}
322358
return null;
323359
}
324360

325361
const content = await response.text();
326-
processedFiles++;
327362

363+
// Update progress and success count atomically
364+
completedCount++;
365+
successCount++;
328366
if (onProgress) {
329367
onProgress({
330368
phase: 'fetching',
331-
current: processedFiles,
332-
total: totalFiles
369+
current: completedCount,
370+
total: totalFiles,
371+
details: {
372+
currentFile: file.path,
373+
successCount: successCount,
374+
failureCount: failureCount
375+
}
333376
});
334377
}
335378

@@ -339,12 +382,20 @@ export class RepositoryCrawler {
339382
originalError: error.message,
340383
filePath: file.path
341384
});
342-
processedFiles++;
385+
386+
// Update progress and failure count atomically
387+
completedCount++;
388+
failureCount++;
343389
if (onProgress) {
344390
onProgress({
345391
phase: 'fetching',
346-
current: processedFiles,
347-
total: totalFiles
392+
current: completedCount,
393+
total: totalFiles,
394+
details: {
395+
currentFile: file.path,
396+
successCount: successCount,
397+
failureCount: failureCount
398+
}
348399
});
349400
}
350401
return null;
@@ -355,21 +406,59 @@ export class RepositoryCrawler {
355406
const results = await Promise.all(filePromises);
356407
filesWithContent.push(...results.filter(f => f !== null));
357408

358-
console.log(`Successfully fetched ${filesWithContent.length} files`);
409+
// Calculate final statistics
410+
const endTime = Date.now();
411+
const duration = Math.round((endTime - startTime) / 1000 * 100) / 100; // seconds with 2 decimal places
412+
const actualSuccessCount = filesWithContent.length;
413+
const actualFailureCount = totalFiles - actualSuccessCount;
414+
const completionRate = totalFiles > 0 ? Math.round((actualSuccessCount / totalFiles) * 100) : 100;
415+
416+
console.log(`Successfully fetched ${actualSuccessCount} files in ${duration}s`);
359417

360-
if (this.errors.length > 0) {
361-
console.warn(`${this.errors.length} non-fatal errors occurred during file fetching`);
418+
if (actualFailureCount > 0) {
419+
console.warn(`${actualFailureCount} files failed to download`);
420+
}
421+
422+
// Send completion summary
423+
if (onProgress) {
424+
onProgress({
425+
phase: 'completed',
426+
current: totalFiles,
427+
total: totalFiles,
428+
details: {
429+
duration: duration,
430+
successCount: actualSuccessCount,
431+
failureCount: actualFailureCount,
432+
completionRate: completionRate,
433+
totalAttempted: totalFiles,
434+
summary: `Scanned ${actualSuccessCount}/${totalFiles} files (${completionRate}%) in ${duration}s`
435+
}
436+
});
362437
}
363438

364439
const result = {
365440
files: filesWithContent,
366441
errors: this.errors,
367-
partial: this.errors.length > 0
442+
partial: actualFailureCount > 0,
443+
scanStats: {
444+
duration: duration,
445+
totalFiles: totalFiles,
446+
successCount: actualSuccessCount,
447+
failureCount: actualFailureCount,
448+
completionRate: completionRate
449+
}
368450
};
369451
this.cache.set(cacheKey, result, 24 * 60 * 60); // Cache for 24 hours
370452

371453
if (onProgress) {
372-
onProgress({ phase: 'analyzing', current: 0, total: filesWithContent.length });
454+
onProgress({
455+
phase: 'analyzing',
456+
current: 0,
457+
total: filesWithContent.length,
458+
details: {
459+
status: 'Starting vulnerability analysis...'
460+
}
461+
});
373462
}
374463

375464
return { ...result, fromCache: false };

0 commit comments

Comments
 (0)