fix(api): SNMP/hosts — одна строка на IP (DISTINCT ON), лимиты для больших сканов
Made-with: Cursor
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
+46
-22
@@ -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
|
||||
}
|
||||
|
||||
@@ -418,8 +418,8 @@
|
||||
deviceTableBody.innerHTML = '<tr><td colspan="5" style="padding:10px;color:#64748b;">Загрузка…</td></tr>';
|
||||
try {
|
||||
const [hRes, sRes, lRes] = await Promise.all([
|
||||
fetch(`/api/scans/${encodeURIComponent(scanId)}/hosts?limit=5000`),
|
||||
fetch(`/api/scans/${encodeURIComponent(scanId)}/snmp?limit=5000`),
|
||||
fetch(`/api/scans/${encodeURIComponent(scanId)}/hosts?limit=200000`),
|
||||
fetch(`/api/scans/${encodeURIComponent(scanId)}/snmp?limit=200000`),
|
||||
fetch(`/api/scans/${encodeURIComponent(scanId)}/lldp?limit=10000`),
|
||||
]);
|
||||
if (!hRes.ok) throw new Error(await hRes.text());
|
||||
|
||||
Reference in New Issue
Block a user