Skip to content

Commit

Permalink
fixed UI ipv4 network sweep device list ordering
Browse files Browse the repository at this point in the history
  • Loading branch information
mfreeman451 committed Jan 29, 2025
1 parent 7bba2fa commit e7d89d4
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 77 deletions.
2 changes: 2 additions & 0 deletions buildCloud.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/bash
set -e

export VERSION=${VERSION}

# Build the builder image
docker build -t serviceradar-builder -f Dockerfile.build .

Expand Down
73 changes: 73 additions & 0 deletions pkg/agent/ipv4_sorter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package agent

import (
"net"
"sort"

"github.com/mfreeman451/serviceradar/pkg/models"
)

// IPSorter is a type for sorting IP addresses

Check failure on line 10 in pkg/agent/ipv4_sorter.go

View workflow job for this annotation

GitHub Actions / lint

Comment should end in a period (godot)
type IPSorter []string

func (s IPSorter) Len() int { return len(s) }

func (s IPSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

func (s IPSorter) Less(i, j int) bool {
ip1 := net.ParseIP(s[i])
ip2 := net.ParseIP(s[j])

// Handle nil cases (invalid IPs) by falling back to string comparison
if ip1 == nil || ip2 == nil {
return s[i] < s[j]
}

// Convert to 4-byte representation for IPv4
ip1 = ip1.To4()
ip2 = ip2.To4()
if ip1 == nil || ip2 == nil {
return s[i] < s[j]
}

// Compare each byte
for i := 0; i < 4; i++ {
if ip1[i] != ip2[i] {
return ip1[i] < ip2[i]
}
}
return false
}

// SortIPList sorts a list of IP addresses in natural order

Check failure on line 42 in pkg/agent/ipv4_sorter.go

View workflow job for this annotation

GitHub Actions / lint

Comment should end in a period (godot)
func SortIPList(ips []string) {
sort.Sort(IPSorter(ips))
}

func SortHostResults(hosts []models.HostResult) {
// Create a custom sorter type for HostResults
sort.Slice(hosts, func(i, j int) bool {
ip1 := net.ParseIP(hosts[i].Host)
ip2 := net.ParseIP(hosts[j].Host)

// Handle nil cases
if ip1 == nil || ip2 == nil {
return hosts[i].Host < hosts[j].Host
}

// Convert to 4-byte representation
ip1 = ip1.To4()
ip2 = ip2.To4()
if ip1 == nil || ip2 == nil {
return hosts[i].Host < hosts[j].Host
}

// Compare each byte
for k := 0; k < 4; k++ {
if ip1[k] != ip2[k] {
return ip1[k] < ip2[k]
}
}
return false
})
}
150 changes: 86 additions & 64 deletions pkg/agent/sweep_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"fmt"
"log"
"net"
"sort"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -121,6 +124,67 @@ func (s *SweepService) Start(ctx context.Context) error {
}
}

func (s *SweepService) generateTargets() ([]models.Target, error) {
var allTargets []models.Target

// Track total unique IPs for logging
uniqueIPs := make(map[string]struct{})

for _, network := range s.config.Networks {
// Parse the CIDR
_, ipNet, err := net.ParseCIDR(network)
if err != nil {
return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err)
}

// Generate IPs - include ALL addresses in range including network/broadcast
var ips []net.IP
for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) {
ips = append(ips, cloneIP(ip))
}

// Add targets for each IP
for _, ip := range ips {
ipStr := ip.String()
uniqueIPs[ipStr] = struct{}{}

// Add ICMP target if enabled
if containsMode(s.config.SweepModes, models.ModeICMP) {
allTargets = append(allTargets, models.Target{
Host: ipStr,
Mode: models.ModeICMP,
Metadata: map[string]interface{}{
"network": network,
},
})
}

// Add TCP targets for each port if enabled
if containsMode(s.config.SweepModes, models.ModeTCP) {
for _, port := range s.config.Ports {
allTargets = append(allTargets, models.Target{
Host: ipStr,
Port: port,
Mode: models.ModeTCP,
Metadata: map[string]interface{}{
"network": network,
},
})
}
}
}
}

log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)",
len(allTargets),
len(s.config.Networks),
len(uniqueIPs),
len(s.config.Ports),
s.config.SweepModes)

return allTargets, nil
}

func (s *SweepService) performSweep(ctx context.Context) error {

Check failure on line 188 in pkg/agent/sweep_service.go

View workflow job for this annotation

GitHub Actions / lint

cyclomatic complexity 12 of func `(*SweepService).performSweep` is high (> 10) (gocyclo)
// Generate targets
targets, err := s.generateTargets()
Expand Down Expand Up @@ -201,67 +265,6 @@ func (s *SweepService) performSweep(ctx context.Context) error {
return nil
}

func (s *SweepService) generateTargets() ([]models.Target, error) {
var allTargets []models.Target

// Track total unique IPs for logging
uniqueIPs := make(map[string]struct{})

for _, network := range s.config.Networks {
// Parse the CIDR
_, ipNet, err := net.ParseCIDR(network)
if err != nil {
return nil, fmt.Errorf("failed to parse CIDR %s: %w", network, err)
}

// Generate IPs - include ALL addresses in range including network/broadcast
var ips []net.IP
for ip := cloneIP(ipNet.IP); ipNet.Contains(ip); incrementIP(ip) {
ips = append(ips, cloneIP(ip))
}

// Add targets for each IP
for _, ip := range ips {
ipStr := ip.String()
uniqueIPs[ipStr] = struct{}{}

// Add ICMP target if enabled
if containsMode(s.config.SweepModes, models.ModeICMP) {
allTargets = append(allTargets, models.Target{
Host: ipStr,
Mode: models.ModeICMP,
Metadata: map[string]interface{}{
"network": network,
},
})
}

// Add TCP targets for each port if enabled
if containsMode(s.config.SweepModes, models.ModeTCP) {
for _, port := range s.config.Ports {
allTargets = append(allTargets, models.Target{
Host: ipStr,
Port: port,
Mode: models.ModeTCP,
Metadata: map[string]interface{}{
"network": network,
},
})
}
}
}
}

log.Printf("Generated %d targets across %d networks (%d unique IPs, %d ports, modes: %v)",
len(allTargets),
len(s.config.Networks),
len(uniqueIPs),
len(s.config.Ports),
s.config.SweepModes)

return allTargets, nil
}

func (s *SweepService) Stop() error {
close(s.closed)
return s.scanner.Stop()
Expand All @@ -277,28 +280,47 @@ func (s *SweepService) GetStatus(ctx context.Context) (*proto.StatusResponse, er
return nil, fmt.Errorf("failed to get sweep summary: %w", err)
}

// Format sweep data for response
data := struct {
Networks []string `json:"networks"`
Network string `json:"network"`
TotalHosts int `json:"total_hosts"`
AvailableHosts int `json:"available_hosts"`
LastSweep int64 `json:"last_sweep"`
Ports []models.PortCount `json:"ports"`
Hosts []models.HostResult `json:"hosts"`
}{
Networks: s.config.Networks,
Network: strings.Join(s.config.Networks, ","),
TotalHosts: summary.TotalHosts,
AvailableHosts: summary.AvailableHosts,
LastSweep: summary.LastSweep,
Ports: summary.Ports,
Hosts: summary.Hosts,
}

// Sort hosts based on IP address numeric values
sort.Slice(data.Hosts, func(i, j int) bool {
// Split IP addresses into their numeric components
ip1Parts := strings.Split(data.Hosts[i].Host, ".")
ip2Parts := strings.Split(data.Hosts[j].Host, ".")

// Compare each octet numerically
for k := 0; k < 4; k++ {
n1, _ := strconv.Atoi(ip1Parts[k])
n2, _ := strconv.Atoi(ip2Parts[k])
if n1 != n2 {
return n1 < n2
}
}
return false
})

statusJSON, err := json.Marshal(data)
if err != nil {
return nil, fmt.Errorf("failed to marshal sweep status: %w", err)
}

// Log the response data for debugging
log.Printf("Sweep status response: %s", string(statusJSON))

return &proto.StatusResponse{
Available: true,
Message: string(statusJSON),
Expand Down
2 changes: 1 addition & 1 deletion pkg/cloud/api/web/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<link rel="shortcut icon" href="/favicons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
<link rel="manifest" href="/favicons/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-B0moJ6A2.js"></script>
<script type="module" crossorigin src="/assets/index-g5oaEaVl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C69mIfJL.css">
</head>
<body>
Expand Down
2 changes: 1 addition & 1 deletion setup-deb-cloud.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -e # Exit on any error

echo "Setting up package structure..."

VERSION=${VERSION:-1.0.9}
VERSION=${VERSION:-1.0.10}

# Create package directory structure
PKG_ROOT="serviceradar-cloud_${VERSION}"
Expand Down
2 changes: 1 addition & 1 deletion web/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<link rel="shortcut icon" href="/favicons/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon.png" />
<link rel="manifest" href="/favicons/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-B0moJ6A2.js"></script>
<script type="module" crossorigin src="/assets/index-g5oaEaVl.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C69mIfJL.css">
</head>
<body>
Expand Down
30 changes: 20 additions & 10 deletions web/src/components/NetworkSweepView.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import React, { useState } from 'react';
import ExportButton from './ExportButton';

const compareIPAddresses = (ip1, ip2) => {
// Split IPs into their octets and convert to numbers
const ip1Parts = ip1.split('.').map(Number);
const ip2Parts = ip2.split('.').map(Number);

// Compare each octet
for (let i = 0; i < 4; i++) {
if (ip1Parts[i] !== ip2Parts[i]) {
return ip1Parts[i] - ip2Parts[i];
}
}
return 0;
};

// Host details subcomponent with ICMP and port results
const HostDetailsView = ({ host }) => {
const formatResponseTime = (ns) => {
Expand Down Expand Up @@ -79,15 +93,11 @@ const NetworkSweepView = ({ nodeId, service, standalone = false }) => {
: service.details;

// Sort and filter hosts
const sortHosts = (hosts) => {
return [...hosts].sort((a, b) => {
const aMatch = a.host.match(/(\d+)$/);
const bMatch = b.host.match(/(\d+)$/);
if (aMatch && bMatch) {
return parseInt(aMatch[1]) - parseInt(bMatch[1]);
}
return a.host.localeCompare(b.host);
});
const sortAndFilterHosts = (hosts) => {
if (!hosts) return [];
return [...hosts]
.filter(host => host.host.toLowerCase().includes(searchTerm.toLowerCase()))
.sort((a, b) => compareIPAddresses(a.host, b.host));
};

// Get responding hosts only
Expand Down Expand Up @@ -116,7 +126,7 @@ const NetworkSweepView = ({ nodeId, service, standalone = false }) => {

// Filter and sort hosts for display
const filteredHosts = sweepDetails.hosts
? sortHosts(respondingHosts).filter(host =>
? sortAndFilterHosts(respondingHosts).filter(host =>
host.host.toLowerCase().includes(searchTerm.toLowerCase())
)
: [];
Expand Down

0 comments on commit e7d89d4

Please sign in to comment.