Skip to content

Commit 02c3c16

Browse files
committed
feat: display database size on frontend via /stats endpoint
1 parent 7247a67 commit 02c3c16

6 files changed

Lines changed: 35 additions & 0 deletions

File tree

src/where_the_plow/db.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# src/where_the_plow/db.py
2+
import os
3+
24
import duckdb
35
from datetime import datetime
46
from itertools import groupby
@@ -301,10 +303,15 @@ def get_stats(self) -> dict:
301303
active_vehicles = self.conn.execute(
302304
"SELECT count(DISTINCT vehicle_id) FROM positions WHERE is_driving = 'maybe'"
303305
).fetchone()[0]
306+
try:
307+
db_size_bytes = os.path.getsize(self.path)
308+
except OSError:
309+
db_size_bytes = None
304310
result = {
305311
"total_positions": total_positions,
306312
"total_vehicles": total_vehicles,
307313
"active_vehicles": active_vehicles,
314+
"db_size_bytes": db_size_bytes,
308315
}
309316
if total_positions > 0:
310317
row = self.conn.execute(

src/where_the_plow/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,4 @@ class StatsResponse(BaseModel):
8888
)
8989
earliest: str | None = Field(None, description="Earliest position timestamp")
9090
latest: str | None = Field(None, description="Latest position timestamp")
91+
db_size_bytes: int | None = Field(None, description="Database file size in bytes")

src/where_the_plow/routes.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,5 @@ def get_stats(request: Request):
202202
active_vehicles=stats.get("active_vehicles", 0),
203203
earliest=earliest.isoformat() if earliest else None,
204204
latest=latest.isoformat() if latest else None,
205+
db_size_bytes=stats.get("db_size_bytes"),
205206
)

src/where_the_plow/static/index.html

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
font-size: 12px;
5151
margin-top: 6px;
5252
}
53+
#db-size {
54+
color: #9ca3af;
55+
font-size: 12px;
56+
}
5357
#vehicle-detail {
5458
margin-top: 10px;
5559
padding-top: 10px;
@@ -164,6 +168,7 @@ <h3>Where the Plow</h3>
164168
<button id="btn-coverage">Coverage</button>
165169
</div>
166170
<div id="vehicle-count">Loading vehicles...</div>
171+
<div id="db-size"></div>
167172
<div id="vehicle-detail">
168173
<span class="detail-close" id="detail-close">&times;</span>
169174
<div class="detail-name" id="detail-name"></div>
@@ -214,10 +219,24 @@ <h3>Where the Plow</h3>
214219
return VEHICLE_COLORS[type] || DEFAULT_COLOR;
215220
}
216221

222+
function formatBytes(bytes) {
223+
if (bytes === null || bytes === undefined) return '';
224+
if (bytes < 1024) return bytes + ' B';
225+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
226+
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
227+
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
228+
}
229+
217230
function updateVehicleCount(data) {
218231
const count = data.features ? data.features.length : 0;
219232
document.getElementById('vehicle-count').textContent =
220233
count + ' vehicle' + (count !== 1 ? 's' : '') + ' tracked';
234+
fetch('/stats').then(r => r.json()).then(stats => {
235+
if (stats.db_size_bytes) {
236+
document.getElementById('db-size').textContent =
237+
formatBytes(stats.db_size_bytes) + ' of data';
238+
}
239+
}).catch(() => {});
221240
}
222241

223242
async function fetchVehicles() {
@@ -464,6 +483,7 @@ <h3>Where the Plow</h3>
464483
map.setLayoutProperty('vehicle-circles', 'visibility', 'visible');
465484
}
466485
document.getElementById('vehicle-count').style.display = '';
486+
document.getElementById('db-size').style.display = '';
467487
startAutoRefresh();
468488
}
469489

@@ -477,6 +497,7 @@ <h3>Where the Plow</h3>
477497
map.setLayoutProperty('vehicle-circles', 'visibility', 'none');
478498
}
479499
document.getElementById('vehicle-count').style.display = 'none';
500+
document.getElementById('db-size').style.display = 'none';
480501
coveragePanelEl.style.display = 'block';
481502
coverageLoading.style.display = 'block';
482503
timeSlider.value = 1000;

tests/test_db.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ def test_get_stats_empty():
192192
stats = db.get_stats()
193193
assert stats["total_positions"] == 0
194194
assert stats["total_vehicles"] == 0
195+
assert stats["db_size_bytes"] is not None
196+
assert stats["db_size_bytes"] > 0
195197
db.close()
196198
os.unlink(path)
197199

tests/test_routes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ def test_get_stats(test_client):
177177
data = resp.json()
178178
assert data["total_positions"] == 4
179179
assert data["total_vehicles"] == 2
180+
assert "db_size_bytes" in data
181+
assert data["db_size_bytes"] is not None
182+
assert data["db_size_bytes"] > 0
180183

181184

182185
def test_openapi_spec(test_client):

0 commit comments

Comments
 (0)