Add scan queue execution and listing endpoint.
Run scans in background with progress/status updates and store scan metrics in memory or PostgreSQL. Made-with: Cursor
This commit is contained in:
+91
-14
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"net"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -33,14 +34,31 @@ type ScanJob struct {
|
||||
ExcludeIPs []string `json:"exclude_ips"`
|
||||
SNMPCredentialsID string `json:"snmp_credentials_id"`
|
||||
Options ScanOptions `json:"options"`
|
||||
Progress int `json:"progress"`
|
||||
Stats ScanStats `json:"stats"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
StartedAt time.Time `json:"started_at,omitempty"`
|
||||
FinishedAt time.Time `json:"finished_at,omitempty"`
|
||||
}
|
||||
|
||||
type ScanStats struct {
|
||||
HostsTotal int `json:"hosts_total"`
|
||||
HostsUp int `json:"hosts_up"`
|
||||
}
|
||||
|
||||
type ScanUpdate struct {
|
||||
Status string
|
||||
Progress int
|
||||
Stats ScanStats
|
||||
StartedAt *time.Time
|
||||
FinishedAt *time.Time
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
CreateScan(CreateScanRequest) (ScanJob, error)
|
||||
GetScan(id string) (ScanJob, bool)
|
||||
ListScans(limit int) []ScanJob
|
||||
UpdateScan(id string, update ScanUpdate) (ScanJob, bool)
|
||||
}
|
||||
|
||||
type MemoryStore struct {
|
||||
@@ -55,20 +73,8 @@ func NewMemoryStore() *MemoryStore {
|
||||
}
|
||||
|
||||
func (s *MemoryStore) CreateScan(req CreateScanRequest) (ScanJob, error) {
|
||||
if len(req.CIDRs) == 0 {
|
||||
return ScanJob{}, errors.New("cidrs must not be empty")
|
||||
}
|
||||
|
||||
for _, cidr := range req.CIDRs {
|
||||
if _, _, err := net.ParseCIDR(cidr); err != nil {
|
||||
return ScanJob{}, errors.New("invalid cidr: " + cidr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ip := range req.ExcludeIPs {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return ScanJob{}, errors.New("invalid exclude ip: " + ip)
|
||||
}
|
||||
if err := validateCreateScanRequest(req); err != nil {
|
||||
return ScanJob{}, err
|
||||
}
|
||||
|
||||
applyDefaultOptions(&req.Options)
|
||||
@@ -87,6 +93,8 @@ func (s *MemoryStore) CreateScan(req CreateScanRequest) (ScanJob, error) {
|
||||
ExcludeIPs: req.ExcludeIPs,
|
||||
SNMPCredentialsID: req.SNMPCredentialsID,
|
||||
Options: req.Options,
|
||||
Progress: 0,
|
||||
Stats: ScanStats{},
|
||||
CreatedAt: now,
|
||||
}
|
||||
|
||||
@@ -104,6 +112,55 @@ func (s *MemoryStore) GetScan(id string) (ScanJob, bool) {
|
||||
return job, ok
|
||||
}
|
||||
|
||||
func (s *MemoryStore) ListScans(limit int) []ScanJob {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
s.mu.RLock()
|
||||
jobs := make([]ScanJob, 0, len(s.jobs))
|
||||
for _, j := range s.jobs {
|
||||
jobs = append(jobs, j)
|
||||
}
|
||||
s.mu.RUnlock()
|
||||
|
||||
sort.Slice(jobs, func(i, j int) bool {
|
||||
return jobs[i].CreatedAt.After(jobs[j].CreatedAt)
|
||||
})
|
||||
if len(jobs) > limit {
|
||||
jobs = jobs[:limit]
|
||||
}
|
||||
|
||||
return jobs
|
||||
}
|
||||
|
||||
func (s *MemoryStore) UpdateScan(id string, update ScanUpdate) (ScanJob, bool) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
job, ok := s.jobs[id]
|
||||
if !ok {
|
||||
return ScanJob{}, false
|
||||
}
|
||||
|
||||
if update.Status != "" {
|
||||
job.Status = update.Status
|
||||
}
|
||||
if update.Progress >= 0 {
|
||||
job.Progress = update.Progress
|
||||
}
|
||||
job.Stats = update.Stats
|
||||
if update.StartedAt != nil {
|
||||
job.StartedAt = *update.StartedAt
|
||||
}
|
||||
if update.FinishedAt != nil {
|
||||
job.FinishedAt = *update.FinishedAt
|
||||
}
|
||||
|
||||
s.jobs[id] = job
|
||||
return job, true
|
||||
}
|
||||
|
||||
func applyDefaultOptions(opts *ScanOptions) {
|
||||
if opts.PingTimeoutMS <= 0 {
|
||||
opts.PingTimeoutMS = 700
|
||||
@@ -119,6 +176,26 @@ func applyDefaultOptions(opts *ScanOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
func validateCreateScanRequest(req CreateScanRequest) error {
|
||||
if len(req.CIDRs) == 0 {
|
||||
return errors.New("cidrs must not be empty")
|
||||
}
|
||||
|
||||
for _, cidr := range req.CIDRs {
|
||||
if _, _, err := net.ParseCIDR(cidr); err != nil {
|
||||
return errors.New("invalid cidr: " + cidr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, ip := range req.ExcludeIPs {
|
||||
if net.ParseIP(ip) == nil {
|
||||
return errors.New("invalid exclude ip: " + ip)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newID() (string, error) {
|
||||
var b [16]byte
|
||||
if _, err := rand.Read(b[:]); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user