mirror of
https://github.com/lunchcat/sif.git
synced 2026-07-28 22:40:54 -07:00
* refactor(cli): extract per-target scan into scanTarget pull the target loop body into scanTarget, which returns a targetScan holding that target's findings, report rows, scan labels and log files instead of appending onto run-wide slices. the run loop merges those in target order and finishRun handles the post-loop notify/silent/report/ summary steps. behavior is identical for the sequential run today; the isolation is the seam a bounded worker pool needs next. * feat(cli): scan targets in parallel with -concurrency add -concurrency N (default 1) to run the per-target scan pool over several targets at once. scanTarget already returns isolated accumulators, so workers share only the console: output.SetConcurrent serializes sink writes and de-animates spinners/progress so parallel lines stay whole. results merge in input order, and concurrency 1 keeps the sequential path unchanged.
198 lines
4.6 KiB
Go
198 lines
4.6 KiB
Go
/*
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
: :
|
|
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
|
|
: ▄█ █ █▀ · BSD 3-Clause License :
|
|
: :
|
|
: (c) 2022-2026 vmfunc, xyzeva, :
|
|
: lunchcat alumni & contributors :
|
|
: :
|
|
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
|
|
*/
|
|
|
|
package output
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
// Progress bar configuration
|
|
const (
|
|
progressWidth = 30
|
|
progressFilled = "="
|
|
progressCurrent = ">"
|
|
progressEmpty = " "
|
|
)
|
|
|
|
// Progress displays a progress bar for operations with known counts
|
|
type Progress struct {
|
|
total int64
|
|
current int64
|
|
message string
|
|
lastItem string
|
|
mu sync.Mutex
|
|
paused bool
|
|
lastShown int // last printed milestone bucket in non-tty mode
|
|
}
|
|
|
|
// NewProgress creates a new progress bar
|
|
func NewProgress(total int, message string) *Progress {
|
|
return &Progress{
|
|
total: int64(total),
|
|
message: message,
|
|
}
|
|
}
|
|
|
|
// Increment advances the progress by 1 and optionally updates the current item
|
|
func (p *Progress) Increment(item string) {
|
|
atomic.AddInt64(&p.current, 1)
|
|
|
|
p.mu.Lock()
|
|
p.lastItem = item
|
|
paused := p.paused
|
|
p.mu.Unlock()
|
|
|
|
if !paused {
|
|
p.render()
|
|
}
|
|
}
|
|
|
|
// Set sets the progress to a specific value
|
|
func (p *Progress) Set(current int, item string) {
|
|
atomic.StoreInt64(&p.current, int64(current))
|
|
|
|
p.mu.Lock()
|
|
p.lastItem = item
|
|
paused := p.paused
|
|
p.mu.Unlock()
|
|
|
|
if !paused {
|
|
p.render()
|
|
}
|
|
}
|
|
|
|
// Pause temporarily stops rendering (use before printing other output)
|
|
func (p *Progress) Pause() {
|
|
p.mu.Lock()
|
|
p.paused = true
|
|
p.mu.Unlock()
|
|
ClearLine()
|
|
}
|
|
|
|
// Resume resumes rendering after a pause
|
|
func (p *Progress) Resume() {
|
|
p.mu.Lock()
|
|
p.paused = false
|
|
p.mu.Unlock()
|
|
p.render()
|
|
}
|
|
|
|
// Done clears the progress bar line
|
|
func (p *Progress) Done() {
|
|
// under -concurrency there is no live line to clear (render printed milestones),
|
|
// and clearing would wipe another target's output.
|
|
if apiMode || !IsTTY || concurrent {
|
|
return
|
|
}
|
|
ClearLine()
|
|
}
|
|
|
|
func (p *Progress) render() {
|
|
if apiMode || silent {
|
|
return
|
|
}
|
|
|
|
// In non-TTY mode, or under -concurrency, print progress at milestones only
|
|
// rather than redrawing a live line that parallel targets would corrupt.
|
|
if !IsTTY || concurrent {
|
|
current := atomic.LoadInt64(&p.current)
|
|
total := p.total
|
|
if total <= 0 {
|
|
return
|
|
}
|
|
percent := int(current * 100 / total)
|
|
|
|
// map current to a milestone bucket (0=none,1..5). concurrent workers
|
|
// hammer the same bucket, so only print when the bucket advances.
|
|
bucket := 0
|
|
switch {
|
|
case current >= total:
|
|
bucket = 5
|
|
case percent >= 75:
|
|
bucket = 4
|
|
case percent >= 50:
|
|
bucket = 3
|
|
case percent >= 25:
|
|
bucket = 2
|
|
case current >= 1:
|
|
bucket = 1
|
|
}
|
|
|
|
p.mu.Lock()
|
|
advanced := bucket > p.lastShown
|
|
if advanced {
|
|
p.lastShown = bucket
|
|
}
|
|
p.mu.Unlock()
|
|
|
|
if advanced {
|
|
fmt.Fprintf(sink, " [%d%%] %d/%d\n", percent, current, total)
|
|
}
|
|
return
|
|
}
|
|
|
|
current := atomic.LoadInt64(&p.current)
|
|
total := p.total
|
|
|
|
p.mu.Lock()
|
|
lastItem := p.lastItem
|
|
p.mu.Unlock()
|
|
|
|
// Calculate percentage
|
|
percent := 0
|
|
if total > 0 {
|
|
percent = int(current * 100 / total)
|
|
}
|
|
|
|
// Build progress bar
|
|
filled := 0
|
|
if total > 0 {
|
|
filled = int(progressWidth * current / total)
|
|
}
|
|
if filled > progressWidth {
|
|
filled = progressWidth
|
|
}
|
|
|
|
bar := ""
|
|
for i := 0; i < progressWidth; i++ {
|
|
switch {
|
|
case i < filled:
|
|
bar += progressFilled
|
|
case i == filled && current < total:
|
|
bar += progressCurrent
|
|
default:
|
|
bar += progressEmpty
|
|
}
|
|
}
|
|
|
|
// Truncate item if too long
|
|
maxItemLen := 30
|
|
if len(lastItem) > maxItemLen {
|
|
lastItem = lastItem[:maxItemLen-3] + "..."
|
|
}
|
|
|
|
// Format: [========> ] 45% (4500/10000) /admin
|
|
line := fmt.Sprintf(" [%s] %3d%% (%d/%d) %s",
|
|
prefixInfo.Render(bar),
|
|
percent,
|
|
current,
|
|
total,
|
|
Muted.Render(lastItem),
|
|
)
|
|
|
|
ClearLine()
|
|
fmt.Fprint(sink, line)
|
|
}
|