diff --git a/internal/api/handler.go b/internal/api/handler.go index 2685f16..5bda749 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -181,11 +181,11 @@ func (h *Handler) getScanHosts(w http.ResponseWriter, r *http.Request) { return } - limit := 500 + limit := 200000 if raw := r.URL.Query().Get("limit"); raw != "" { n, err := strconv.Atoi(raw) - if err != nil || n <= 0 || n > 5000 { - writeError(w, http.StatusBadRequest, "limit must be between 1 and 5000") + if err != nil || n <= 0 || n > 500000 { + writeError(w, http.StatusBadRequest, "limit must be between 1 and 500000") return } limit = n @@ -233,11 +233,11 @@ func (h *Handler) getScanSNMP(w http.ResponseWriter, r *http.Request) { return } - limit := 1000 + limit := 200000 if raw := r.URL.Query().Get("limit"); raw != "" { n, err := strconv.Atoi(raw) - if err != nil || n <= 0 || n > 10000 { - writeError(w, http.StatusBadRequest, "limit must be between 1 and 10000") + if err != nil || n <= 0 || n > 500000 { + writeError(w, http.StatusBadRequest, "limit must be between 1 and 500000") return } limit = n diff --git a/internal/scans/postgres_store.go b/internal/scans/postgres_store.go index 1c13e53..02536f5 100644 --- a/internal/scans/postgres_store.go +++ b/internal/scans/postgres_store.go @@ -291,12 +291,17 @@ values ($1, $2, $3, $4)` func (s *PostgresStore) ListHostResults(scanID string, limit int) []HostResult { if limit <= 0 { - limit = 500 + limit = 200000 } + // Одна актуальная строка на IP (иначе при limit по времени часть хостов без SNMP в UI). query := ` select ip::text, is_up, checked_at -from scan_hosts -where scan_id = $1 +from ( + select distinct on (ip) ip, is_up, checked_at + from scan_hosts + where scan_id = $1 + order by ip asc, checked_at desc +) t order by checked_at desc limit $2` rows, err := s.db.QueryContext(context.Background(), query, scanID, limit) @@ -380,13 +385,17 @@ values ($1, $2, $3, $4, $5, $6, $7, $8)` func (s *PostgresStore) ListSNMPResults(scanID string, limit int) []SNMPResult { if limit <= 0 { - limit = 1000 + limit = 200000 } query := ` select ip::text, success, coalesce(sys_name, ''), coalesce(sys_descr, ''), coalesce(sys_object_id, ''), coalesce(error_text, ''), checked_at -from scan_snmp -where scan_id = $1 +from ( + select distinct on (ip) ip, success, sys_name, sys_descr, sys_object_id, error_text, checked_at + from scan_snmp + where scan_id = $1 + order by ip asc, checked_at desc +) t order by checked_at desc limit $2` rows, err := s.db.QueryContext(context.Background(), query, scanID, limit) diff --git a/internal/scans/store.go b/internal/scans/store.go index 07e9674..642bb17 100644 --- a/internal/scans/store.go +++ b/internal/scans/store.go @@ -221,21 +221,11 @@ func (s *MemoryStore) SaveHostResult(scanID string, host HostResult) error { } func (s *MemoryStore) ListHostResults(scanID string, limit int) []HostResult { - if limit <= 0 { - limit = 500 - } s.mu.RLock() defer s.mu.RUnlock() items := s.hosts[scanID] - if len(items) <= limit { - out := make([]HostResult, len(items)) - copy(out, items) - return out - } - out := make([]HostResult, limit) - copy(out, items[:limit]) - return out + return dedupeHostsLatest(items, limit) } func (s *MemoryStore) SaveOpenPortResult(scanID string, result OpenPortResult) error { @@ -279,21 +269,11 @@ func (s *MemoryStore) SaveSNMPResult(scanID string, result SNMPResult) error { } func (s *MemoryStore) ListSNMPResults(scanID string, limit int) []SNMPResult { - if limit <= 0 { - limit = 1000 - } s.mu.RLock() defer s.mu.RUnlock() items := s.snmp[scanID] - if len(items) <= limit { - out := make([]SNMPResult, len(items)) - copy(out, items) - return out - } - out := make([]SNMPResult, limit) - copy(out, items[:limit]) - return out + return dedupeSNMPLatest(items, limit) } func (s *MemoryStore) SaveLLDPResult(scanID string, result LLDPResult) error { @@ -367,3 +347,47 @@ func newID() (string, error) { } return hex.EncodeToString(b[:]), nil } + +func dedupeHostsLatest(items []HostResult, limit int) []HostResult { + if limit <= 0 { + limit = 200000 + } + m := make(map[string]HostResult, len(items)) + for _, h := range items { + cur, ok := m[h.IP] + if !ok || h.CheckedAt.After(cur.CheckedAt) { + m[h.IP] = h + } + } + out := make([]HostResult, 0, len(m)) + for _, h := range m { + out = append(out, h) + } + sort.Slice(out, func(i, j int) bool { return out[i].CheckedAt.After(out[j].CheckedAt) }) + if len(out) > limit { + out = out[:limit] + } + return out +} + +func dedupeSNMPLatest(items []SNMPResult, limit int) []SNMPResult { + if limit <= 0 { + limit = 200000 + } + m := make(map[string]SNMPResult, len(items)) + for _, r := range items { + cur, ok := m[r.IP] + if !ok || r.CheckedAt.After(cur.CheckedAt) { + m[r.IP] = r + } + } + out := make([]SNMPResult, 0, len(m)) + for _, r := range m { + out = append(out, r) + } + sort.Slice(out, func(i, j int) bool { return out[i].CheckedAt.After(out[j].CheckedAt) }) + if len(out) > limit { + out = out[:limit] + } + return out +} diff --git a/internal/webui/index.html b/internal/webui/index.html index d62b4be..2b53efd 100644 --- a/internal/webui/index.html +++ b/internal/webui/index.html @@ -418,8 +418,8 @@ deviceTableBody.innerHTML = '