Merging beta into master #8
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -26,8 +26,11 @@
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value" id="total-ips">-</div>
|
||||
<div class="stat-label">Total IPs</div>
|
||||
<div class="stat-value" id="unique-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 class="col-md-4">
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user