nightly merge into beta #7

Merged
ptarrant merged 7 commits from nightly into beta 2025-11-21 22:05:43 +00:00
4 changed files with 81 additions and 9 deletions
Showing only changes of commit f24bd11dfd - Show all commits

View File

@@ -36,9 +36,15 @@ def list_sites():
if request.args.get('all', '').lower() == 'true': if request.args.get('all', '').lower() == 'true':
site_service = SiteService(current_app.db_session) site_service = SiteService(current_app.db_session)
sites = site_service.list_all_sites() sites = site_service.list_all_sites()
ip_stats = site_service.get_global_ip_stats()
logger.info(f"Listed all sites (count={len(sites)})") 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 # Get and validate query parameters
page = request.args.get('page', 1, type=int) page = request.args.get('page', 1, type=int)

View File

@@ -228,6 +228,34 @@ class SiteService:
return [self._site_to_dict(site) for site in sites] 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, def bulk_add_ips_from_cidr(self, site_id: int, cidr: str,
expected_ping: Optional[bool] = None, expected_ping: Optional[bool] = None,
expected_tcp_ports: Optional[List[int]] = None, expected_tcp_ports: Optional[List[int]] = None,

View File

@@ -26,8 +26,11 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="stat-card"> <div class="stat-card">
<div class="stat-value" id="total-ips">-</div> <div class="stat-value" id="unique-ips">-</div>
<div class="stat-label">Total IPs</div> <div class="stat-label">Unique IPs</div>
<div class="stat-sublabel" id="duplicate-ips-label" style="display: none; font-size: 0.75rem; color: #fbbf24;">
(<span id="duplicate-ips">0</span> duplicates)
</div>
</div> </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
@@ -499,7 +502,7 @@ async function loadSites() {
const data = await response.json(); const data = await response.json();
sitesData = data.sites || []; sitesData = data.sites || [];
updateStats(); updateStats(data.unique_ips, data.duplicate_ips);
renderSites(sitesData); renderSites(sitesData);
document.getElementById('sites-loading').style.display = 'none'; document.getElementById('sites-loading').style.display = 'none';
@@ -514,12 +517,20 @@ async function loadSites() {
} }
// Update summary stats // Update summary stats
function updateStats() { function updateStats(uniqueIps, duplicateIps) {
const totalSites = sitesData.length; 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-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 document.getElementById('sites-in-use').textContent = '-'; // Will be updated async
// Count sites in use (async) // Count sites in use (async)

View File

@@ -117,7 +117,7 @@ Retrieve a paginated list of all sites.
| `per_page` | integer | No | 20 | Items per page (1-100) | | `per_page` | integer | No | 20 | Items per page (1-100) |
| `all` | string | No | - | Set to "true" to return all sites without pagination | | `all` | string | No | - | Set to "true" to return all sites without pagination |
**Success Response (200 OK):** **Success Response (200 OK) - Paginated:**
```json ```json
{ {
"sites": [ "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:** **Usage Example:**
```bash ```bash
# List first page # List first page
curl -X GET http://localhost:5000/api/sites \ curl -X GET http://localhost:5000/api/sites \
-b cookies.txt -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" \ curl -X GET "http://localhost:5000/api/sites?all=true" \
-b cookies.txt -b cookies.txt
``` ```