nightly merge into beta #7
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user