diff --git a/app/web/api/sites.py b/app/web/api/sites.py index 6440c3e..c1ccc5a 100644 --- a/app/web/api/sites.py +++ b/app/web/api/sites.py @@ -36,9 +36,15 @@ def list_sites(): if request.args.get('all', '').lower() == 'true': site_service = SiteService(current_app.db_session) sites = site_service.list_all_sites() + ip_stats = site_service.get_global_ip_stats() logger.info(f"Listed all sites (count={len(sites)})") - return jsonify({'sites': sites}) + return jsonify({ + 'sites': sites, + 'total_ips': ip_stats['total_ips'], + 'unique_ips': ip_stats['unique_ips'], + 'duplicate_ips': ip_stats['duplicate_ips'] + }) # Get and validate query parameters page = request.args.get('page', 1, type=int) diff --git a/app/web/services/site_service.py b/app/web/services/site_service.py index f4a4fb7..739e60d 100644 --- a/app/web/services/site_service.py +++ b/app/web/services/site_service.py @@ -228,6 +228,34 @@ class SiteService: return [self._site_to_dict(site) for site in sites] + def get_global_ip_stats(self) -> Dict[str, int]: + """ + Get global IP statistics across all sites. + + Returns: + Dictionary with: + - total_ips: Total count of IP entries (including duplicates) + - unique_ips: Count of distinct IP addresses + - duplicate_ips: Number of duplicate entries (total - unique) + """ + # Total IP entries + total_ips = ( + self.db.query(func.count(SiteIP.id)) + .scalar() or 0 + ) + + # Unique IP addresses + unique_ips = ( + self.db.query(func.count(func.distinct(SiteIP.ip_address))) + .scalar() or 0 + ) + + return { + 'total_ips': total_ips, + 'unique_ips': unique_ips, + 'duplicate_ips': total_ips - unique_ips + } + def bulk_add_ips_from_cidr(self, site_id: int, cidr: str, expected_ping: Optional[bool] = None, expected_tcp_ports: Optional[List[int]] = None, diff --git a/app/web/templates/sites.html b/app/web/templates/sites.html index 074ba3b..0684fa1 100644 --- a/app/web/templates/sites.html +++ b/app/web/templates/sites.html @@ -26,8 +26,11 @@
-
-
-
Total IPs
+
-
+
Unique IPs
+
@@ -499,7 +502,7 @@ async function loadSites() { const data = await response.json(); sitesData = data.sites || []; - updateStats(); + updateStats(data.unique_ips, data.duplicate_ips); renderSites(sitesData); document.getElementById('sites-loading').style.display = 'none'; @@ -514,12 +517,20 @@ async function loadSites() { } // Update summary stats -function updateStats() { +function updateStats(uniqueIps, duplicateIps) { const totalSites = sitesData.length; - const totalIps = sitesData.reduce((sum, site) => sum + (site.ip_count || 0), 0); document.getElementById('total-sites').textContent = totalSites; - document.getElementById('total-ips').textContent = totalIps; + document.getElementById('unique-ips').textContent = uniqueIps || 0; + + // Show duplicate count if there are any + if (duplicateIps && duplicateIps > 0) { + document.getElementById('duplicate-ips').textContent = duplicateIps; + document.getElementById('duplicate-ips-label').style.display = 'block'; + } else { + document.getElementById('duplicate-ips-label').style.display = 'none'; + } + document.getElementById('sites-in-use').textContent = '-'; // Will be updated async // Count sites in use (async) diff --git a/docs/API_REFERENCE.md b/docs/API_REFERENCE.md index 1bcc7ad..10f4aff 100644 --- a/docs/API_REFERENCE.md +++ b/docs/API_REFERENCE.md @@ -117,7 +117,7 @@ Retrieve a paginated list of all sites. | `per_page` | integer | No | 20 | Items per page (1-100) | | `all` | string | No | - | Set to "true" to return all sites without pagination | -**Success Response (200 OK):** +**Success Response (200 OK) - Paginated:** ```json { "sites": [ @@ -139,13 +139,40 @@ Retrieve a paginated list of all sites. } ``` +**Success Response (200 OK) - All Sites (all=true):** +```json +{ + "sites": [ + { + "id": 1, + "name": "Production DC", + "description": "Production datacenter servers", + "ip_count": 25, + "created_at": "2025-11-19T10:30:00Z", + "updated_at": "2025-11-19T10:30:00Z" + } + ], + "total_ips": 100, + "unique_ips": 85, + "duplicate_ips": 15 +} +``` + +**Response Fields (all=true):** + +| Field | Type | Description | +|-------|------|-------------| +| `total_ips` | integer | Total count of IP entries across all sites (including duplicates) | +| `unique_ips` | integer | Count of distinct IP addresses | +| `duplicate_ips` | integer | Number of duplicate IP entries (total_ips - unique_ips) | + **Usage Example:** ```bash # List first page curl -X GET http://localhost:5000/api/sites \ -b cookies.txt -# Get all sites (for dropdowns) +# Get all sites with global IP stats curl -X GET "http://localhost:5000/api/sites?all=true" \ -b cookies.txt ```