fix: 503 при сбое GetScan вместо ложного 404 scan not found

- Store.GetScan возвращает (job, ok, error); Postgres отдаёт err при ошибке БД/JSON

- API: getScanOrWriteError → 503 scan storage temporarily unavailable

- Web UI: понятное сообщение для HTTP 503 при опросе скана

Made-with: Cursor
This commit is contained in:
PTah
2026-04-13 15:38:04 +10:00
parent 6ad59c9990
commit 0086302605
5 changed files with 74 additions and 45 deletions
+14 -9
View File
@@ -5,6 +5,7 @@ import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"log"
"sort"
"strings"
@@ -98,7 +99,7 @@ insert into scan_jobs (
return job, nil
}
func (s *PostgresStore) GetScan(id string) (ScanJob, bool) {
func (s *PostgresStore) GetScan(id string) (ScanJob, bool, error) {
query := `
select id, name, status, cidrs::text, exclude_ips::text, options::text,
progress, stats::text, coalesce(snmp_credentials_id::text, ''),
@@ -130,28 +131,28 @@ limit 1`
&finishedAt,
)
if errors.Is(err, sql.ErrNoRows) {
return ScanJob{}, false
return ScanJob{}, false, nil
}
if err != nil {
log.Printf("postgres GetScan id=%q: row scan: %v", id, err)
return ScanJob{}, false
return ScanJob{}, false, fmt.Errorf("postgres GetScan id=%q: %w", id, err)
}
if err = json.Unmarshal([]byte(cidrsRaw), &job.CIDRs); err != nil {
log.Printf("postgres GetScan id=%q: cidrs json (len=%d): %v", id, len(cidrsRaw), err)
return ScanJob{}, false
return ScanJob{}, false, fmt.Errorf("postgres GetScan id=%q cidrs: %w", id, err)
}
if err = json.Unmarshal([]byte(excludeRaw), &job.ExcludeIPs); err != nil {
log.Printf("postgres GetScan id=%q: exclude_ips json: %v", id, err)
return ScanJob{}, false
return ScanJob{}, false, fmt.Errorf("postgres GetScan id=%q exclude_ips: %w", id, err)
}
if err = json.Unmarshal([]byte(optionsRaw), &job.Options); err != nil {
log.Printf("postgres GetScan id=%q: options json: %v", id, err)
return ScanJob{}, false
return ScanJob{}, false, fmt.Errorf("postgres GetScan id=%q options: %w", id, err)
}
if err = json.Unmarshal([]byte(statsRaw), &job.Stats); err != nil {
log.Printf("postgres GetScan id=%q: stats json: %v", id, err)
return ScanJob{}, false
return ScanJob{}, false, fmt.Errorf("postgres GetScan id=%q stats: %w", id, err)
}
if startedAt.Valid {
job.StartedAt = startedAt.Time
@@ -160,7 +161,7 @@ limit 1`
job.FinishedAt = finishedAt.Time
}
return job, true
return job, true, nil
}
func (s *PostgresStore) ListScans(limit int) []ScanJob {
@@ -230,7 +231,11 @@ limit $1`
}
func (s *PostgresStore) UpdateScan(id string, update ScanUpdate) (ScanJob, bool) {
current, ok := s.GetScan(id)
current, ok, err := s.GetScan(id)
if err != nil {
log.Printf("postgres UpdateScan: GetScan id=%q: %v", id, err)
return ScanJob{}, false
}
if !ok {
return ScanJob{}, false
}
+9 -2
View File
@@ -88,7 +88,11 @@ func (r *Runner) CancelScan(id string) bool {
}
func (r *Runner) markJobCanceledFromStore(jobID string) {
cur, ok := r.store.GetScan(jobID)
cur, ok, err := r.store.GetScan(jobID)
if err != nil {
log.Printf("runner markJobCanceledFromStore GetScan %q: %v", jobID, err)
return
}
if !ok {
return
}
@@ -106,7 +110,10 @@ func (r *Runner) markJobCanceledFromStore(jobID string) {
}
func (r *Runner) run(ctx context.Context, job ScanJob) {
if cur, ok := r.store.GetScan(job.ID); ok && cur.Status == "canceled" {
if cur, ok, err := r.store.GetScan(job.ID); err != nil {
log.Printf("runner run GetScan %q: %v", job.ID, err)
return
} else if ok && cur.Status == "canceled" {
return
}
if err := ctx.Err(); err != nil && errors.Is(err, context.Canceled) {
+4 -3
View File
@@ -61,7 +61,8 @@ type ScanUpdate struct {
type Store interface {
CreateScan(CreateScanRequest) (ScanJob, error)
GetScan(id string) (ScanJob, bool)
// GetScan: при found==false и err==nil записи нет; err!=nil — сбой хранилища (например БД).
GetScan(id string) (ScanJob, bool, error)
ListScans(limit int) []ScanJob
UpdateScan(id string, update ScanUpdate) (ScanJob, bool)
SaveHostResult(scanID string, host HostResult) error
@@ -195,11 +196,11 @@ func (s *MemoryStore) CreateScan(req CreateScanRequest) (ScanJob, error) {
return job, nil
}
func (s *MemoryStore) GetScan(id string) (ScanJob, bool) {
func (s *MemoryStore) GetScan(id string) (ScanJob, bool, error) {
s.mu.RLock()
job, ok := s.jobs[id]
s.mu.RUnlock()
return job, ok
return job, ok, nil
}
func (s *MemoryStore) ListScans(limit int) []ScanJob {