fix: restore /api/stats route, remove dead player code, fix modal scroll
- app_sql.py: add get_overview_stats() with single SQL query (trip count, total km, duration, max speed, active DTC count) - app.py: add /api/stats route (was missing after scp overwrote the file) - local-videos.js: remove leftover old closePlayer/playVideo/renderFiles duplicate block that still had scrollIntoView and references to the old panel elements; new modal-based code is the only path now Co-authored-by: Copilot <[email protected]>
This commit is contained in:
@@ -255,6 +255,10 @@ def api_dtc():
|
|||||||
"codes": app_obd.get_fresh_dtc_codes(),
|
"codes": app_obd.get_fresh_dtc_codes(),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@app.route('/api/stats')
|
||||||
|
def api_stats():
|
||||||
|
return jsonify(app_sql.get_overview_stats())
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Debug/Development
|
# Debug/Development
|
||||||
# app.run(debug=True, host="0.0.0.0", port="5000")
|
# app.run(debug=True, host="0.0.0.0", port="5000")
|
||||||
|
|||||||
@@ -317,6 +317,32 @@ def refresh_trips(force=False):
|
|||||||
return conn.execute("SELECT COUNT(*) FROM trips").fetchone()[0]
|
return conn.execute("SELECT COUNT(*) FROM trips").fetchone()[0]
|
||||||
|
|
||||||
|
|
||||||
|
def get_overview_stats():
|
||||||
|
conn = get_conn()
|
||||||
|
with _conn_lock:
|
||||||
|
row = conn.execute("""
|
||||||
|
SELECT COUNT(*), COALESCE(SUM(DISTANCE_KM),0),
|
||||||
|
COALESCE(SUM(DURATION_SECONDS),0), COALESCE(MAX(MAX_SPEED_KMH),0)
|
||||||
|
FROM trips
|
||||||
|
""").fetchone()
|
||||||
|
dtc_row = conn.execute("""
|
||||||
|
SELECT COUNT(*) FROM dtc_codes WHERE cleared_at IS NULL
|
||||||
|
""").fetchone() if _table_exists(conn, "dtc_codes") else (0,)
|
||||||
|
return {
|
||||||
|
"trip_count": row[0],
|
||||||
|
"total_km": row[1],
|
||||||
|
"total_duration_s": row[2],
|
||||||
|
"all_time_max_speed": row[3],
|
||||||
|
"active_dtc": dtc_row[0] if dtc_row else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _table_exists(conn, name):
|
||||||
|
return conn.execute(
|
||||||
|
"SELECT 1 FROM sqlite_master WHERE type='table' AND name=?", (name,)
|
||||||
|
).fetchone() is not None
|
||||||
|
|
||||||
|
|
||||||
def list_trips():
|
def list_trips():
|
||||||
refresh_trips()
|
refresh_trips()
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
|
|||||||
@@ -228,142 +228,4 @@
|
|||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closePlayer() {
|
|
||||||
player.pause();
|
|
||||||
player.removeAttribute("src");
|
|
||||||
player.load();
|
|
||||||
playerPanel.hidden = true;
|
|
||||||
playingPath = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function playVideo(path) {
|
|
||||||
playingPath = path;
|
|
||||||
playerTitle.textContent = path;
|
|
||||||
player.src = `/api/local-video/file?path=${encodeURIComponent(path)}`;
|
|
||||||
playerPanel.hidden = false;
|
|
||||||
player.load();
|
|
||||||
player.play().catch(function () {
|
|
||||||
// Browser autoplay policy may require the user to press play.
|
|
||||||
});
|
|
||||||
playerPanel.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderFiles(result) {
|
|
||||||
thumbnailObserver.disconnect();
|
|
||||||
body.replaceChildren();
|
|
||||||
const files = result.files || [];
|
|
||||||
let uploadedCount = 0;
|
|
||||||
|
|
||||||
files.forEach(function (file) {
|
|
||||||
const row = document.createElement("tr");
|
|
||||||
row.dataset.path = file.path;
|
|
||||||
row.dataset.size = file.size;
|
|
||||||
row.dataset.modified = file.modified_at;
|
|
||||||
row.dataset.uploaded = file.uploaded ? "1" : "0";
|
|
||||||
|
|
||||||
const preview = document.createElement("td");
|
|
||||||
const image = document.createElement("img");
|
|
||||||
image.className = "local-video-thumbnail";
|
|
||||||
image.alt = `Preview of ${file.path}`;
|
|
||||||
image.loading = "lazy";
|
|
||||||
image.dataset.path = file.path;
|
|
||||||
image.addEventListener("error", function () {
|
|
||||||
image.hidden = true;
|
|
||||||
});
|
|
||||||
preview.appendChild(image);
|
|
||||||
row.appendChild(preview);
|
|
||||||
thumbnailObserver.observe(image);
|
|
||||||
|
|
||||||
row.appendChild(textCell(file.path));
|
|
||||||
row.appendChild(textCell(YarisUI.formatBytes(file.size)));
|
|
||||||
row.appendChild(textCell(new Date(file.modified_at * 1000).toLocaleString()));
|
|
||||||
|
|
||||||
const upload = document.createElement("td");
|
|
||||||
const badge = document.createElement("span");
|
|
||||||
badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark";
|
|
||||||
badge.textContent = file.uploaded ? "Uploaded" : "Pending";
|
|
||||||
upload.appendChild(badge);
|
|
||||||
row.appendChild(upload);
|
|
||||||
if (file.uploaded) {
|
|
||||||
uploadedCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = document.createElement("td");
|
|
||||||
actions.className = "data-list-actions";
|
|
||||||
const playButton = document.createElement("button");
|
|
||||||
playButton.type = "button";
|
|
||||||
playButton.className = "btn btn-sm btn-outline-primary";
|
|
||||||
playButton.dataset.playPath = file.path;
|
|
||||||
playButton.innerHTML = '<i class="fas fa-play me-1"></i>Play';
|
|
||||||
actions.appendChild(playButton);
|
|
||||||
|
|
||||||
const deleteButton = document.createElement("button");
|
|
||||||
deleteButton.type = "button";
|
|
||||||
deleteButton.className = "btn btn-sm btn-outline-danger";
|
|
||||||
deleteButton.dataset.deleteLocalPath = file.path;
|
|
||||||
deleteButton.dataset.confirm = `Delete the local Pi copy of "${file.path}"? The remote archive will not be changed.`;
|
|
||||||
deleteButton.innerHTML = '<i class="fas fa-trash me-1"></i>Delete local';
|
|
||||||
actions.appendChild(deleteButton);
|
|
||||||
row.appendChild(actions);
|
|
||||||
body.appendChild(row);
|
|
||||||
});
|
|
||||||
|
|
||||||
summary.textContent = `${files.length} video(s), ${YarisUI.formatBytes(result.total_size || 0)} total, ${uploadedCount} uploaded`;
|
|
||||||
if (result.retention_days > 0) {
|
|
||||||
summary.textContent += `; uploaded copies older than ${result.retention_days} day(s) are auto-deleted`;
|
|
||||||
} else {
|
|
||||||
summary.textContent += "; automatic retention cleanup is off";
|
|
||||||
}
|
|
||||||
if (result.errors && result.errors.length) {
|
|
||||||
summary.textContent += ` (${result.errors.length} file error(s))`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function refreshFiles() {
|
|
||||||
refreshButton.disabled = true;
|
|
||||||
summary.textContent = "Loading...";
|
|
||||||
return fetchJson("/api/local-videos")
|
|
||||||
.then(renderFiles)
|
|
||||||
.catch(function (error) {
|
|
||||||
body.replaceChildren();
|
|
||||||
summary.textContent = `Unable to list local videos: ${error.message}`;
|
|
||||||
})
|
|
||||||
.finally(function () {
|
|
||||||
refreshButton.disabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteVideo(path) {
|
|
||||||
return fetchJson("/api/local-video", {
|
|
||||||
method: "DELETE",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({ path }),
|
|
||||||
}).then(function () {
|
|
||||||
if (playingPath === path) {
|
|
||||||
closePlayer();
|
|
||||||
}
|
|
||||||
return refreshFiles();
|
|
||||||
}).catch(function (error) {
|
|
||||||
summary.textContent = `Local delete failed: ${error.message}`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
body.addEventListener("click", function (event) {
|
|
||||||
const playButton = event.target.closest("[data-play-path]");
|
|
||||||
if (playButton) {
|
|
||||||
playVideo(playButton.dataset.playPath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteButton = event.target.closest("[data-delete-local-path]");
|
|
||||||
if (deleteButton) {
|
|
||||||
deleteButton.disabled = true;
|
|
||||||
deleteVideo(deleteButton.dataset.deleteLocalPath).finally(function () {
|
|
||||||
deleteButton.disabled = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
refreshButton.addEventListener("click", refreshFiles);
|
|
||||||
closePlayerButton.addEventListener("click", closePlayer);
|
|
||||||
refreshFiles();
|
|
||||||
}());
|
|
||||||
|
|||||||
Reference in New Issue
Block a user