Merge branch 'main' of https://gitea.vecmocon.com/kirubakaran/SwapStation_WebApp
commit
2c910cfcc5
Binary file not shown.
|
|
@ -5,7 +5,8 @@ import json
|
||||||
import csv
|
import csv
|
||||||
import io
|
import io
|
||||||
import time
|
import time
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone, time as dt_time
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
from flask import Flask, jsonify, request, Response
|
from flask import Flask, jsonify, request, Response
|
||||||
from flask_socketio import SocketIO, join_room
|
from flask_socketio import SocketIO, join_room
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
|
|
@ -55,6 +56,8 @@ app.config['SECRET_KEY'] = os.getenv("SECRET_KEY", "a_very_secret_key")
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
socketio = SocketIO(app, cors_allowed_origins="*")
|
socketio = SocketIO(app, cors_allowed_origins="*")
|
||||||
|
|
||||||
|
IST = ZoneInfo("Asia/Kolkata")
|
||||||
|
|
||||||
# --- User Loader for Flask-Login ---
|
# --- User Loader for Flask-Login ---
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
|
|
@ -283,25 +286,29 @@ def get_stations():
|
||||||
@app.route('/api/stations/daily-stats', methods=['GET'])
|
@app.route('/api/stations/daily-stats', methods=['GET'])
|
||||||
def get_all_station_stats():
|
def get_all_station_stats():
|
||||||
"""
|
"""
|
||||||
Calculates the swap statistics for today for all stations.
|
Calculates the swap statistics for the current calendar day in the IST timezone.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# --- CHANGE THESE TWO LINES ---
|
# Get the current time and date in your timezone (IST is defined globally)
|
||||||
today_start = datetime.combine(datetime.utcnow().date(), time.min)
|
now_ist = datetime.now(IST)
|
||||||
today_end = datetime.combine(datetime.utcnow().date(), time.max)
|
today_ist = now_ist.date()
|
||||||
|
|
||||||
# This is an efficient query that groups by station_id and counts events in one go
|
# Calculate the precise start and end of that day
|
||||||
|
start_of_day_ist = datetime.combine(today_ist, dt_time.min, tzinfo=IST)
|
||||||
|
end_of_day_ist = datetime.combine(today_ist, dt_time.max, tzinfo=IST)
|
||||||
|
|
||||||
|
# --- The rest of the query uses this new date range ---
|
||||||
stats = db.session.query(
|
stats = db.session.query(
|
||||||
MqttLog.station_id,
|
MqttLog.station_id,
|
||||||
func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_START', 1))).label('total_starts'),
|
func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_START', 1))).label('total_starts'),
|
||||||
func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_ENDED', 1))).label('completed'),
|
func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_ENDED', 1))).label('completed'),
|
||||||
func.count(case((MqttLog.payload['eventType'] == 'EVENT_SWAP_ABORTED', 1))).label('aborted')
|
func.count(case((MqttLog.payload['eventType'].astext == 'EVENT_SWAP_ABORTED', 1))).label('aborted')
|
||||||
).filter(
|
).filter(
|
||||||
MqttLog.topic_type == 'EVENTS',
|
MqttLog.topic_type == 'EVENTS',
|
||||||
MqttLog.timestamp.between(today_start, today_end)
|
# Use the new timezone-aware date range
|
||||||
|
MqttLog.timestamp.between(start_of_day_ist, end_of_day_ist)
|
||||||
).group_by(MqttLog.station_id).all()
|
).group_by(MqttLog.station_id).all()
|
||||||
|
|
||||||
# Convert the list of tuples into a dictionary for easy lookup
|
|
||||||
stats_dict = {
|
stats_dict = {
|
||||||
station_id: {
|
station_id: {
|
||||||
"total_starts": total_starts,
|
"total_starts": total_starts,
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
//-- NEW: Fetch and apply daily stats to each card ---
|
//-- NEW: Fetch and apply daily stats to each card ---
|
||||||
const fetchAndApplyStats = async () => {
|
const fetchAndApplyStats = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/stations/daily-stats`);
|
const response = await fetch(`${API_BASE}/stations/daily-stats`, {
|
||||||
|
method: 'GET',
|
||||||
|
credentials: 'include' // <-- ADD THIS LINE
|
||||||
|
});
|
||||||
if (!response.ok) return; // Fail silently if stats aren't available
|
if (!response.ok) return; // Fail silently if stats aren't available
|
||||||
const stats = await response.json();
|
const stats = await response.json();
|
||||||
|
|
||||||
|
|
@ -158,7 +161,10 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/stations/${stationId}`, { method: 'DELETE' });
|
const response = await fetch(`${API_BASE}/stations/${stationId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'include' // <-- ADD THIS LINE
|
||||||
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
alert(`Station "${stationName}" removed successfully.`);
|
alert(`Station "${stationName}" removed successfully.`);
|
||||||
allStations = []; // Force a full refresh on next poll
|
allStations = []; // Force a full refresh on next poll
|
||||||
|
|
@ -175,26 +181,61 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- DATA FETCHING & POLLING ---
|
// --- DATA FETCHING & POLLING ---
|
||||||
|
// const loadAndPollStations = async () => {
|
||||||
|
// try {
|
||||||
|
// const response = await fetch(`${API_BASE}/stations`, {
|
||||||
|
// method: 'GET',
|
||||||
|
// credentials: 'include' // <-- ADD THIS LINE
|
||||||
|
// });
|
||||||
|
// if (!response.ok) throw new Error('Failed to fetch stations');
|
||||||
|
|
||||||
|
// const newStationList = await response.json();
|
||||||
|
|
||||||
|
// // If the number of stations has changed, we must do a full re-render.
|
||||||
|
// if (newStationList.length !== allStations.length) {
|
||||||
|
// allStations = newStationList;
|
||||||
|
// renderStations(allStations);
|
||||||
|
// } else {
|
||||||
|
// // Otherwise, we can do a more efficient status-only update.
|
||||||
|
// allStations = newStationList;
|
||||||
|
// updateStationStatuses(allStations);
|
||||||
|
// fetchAndApplyStats(); // Fetch and update daily stats
|
||||||
|
// }
|
||||||
|
// } catch (error) {
|
||||||
|
// console.error(error);
|
||||||
|
// stationCountEl.textContent = 'Could not load stations. Is the backend running?';
|
||||||
|
// if (pollingInterval) clearInterval(pollingInterval);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
const loadAndPollStations = async () => {
|
const loadAndPollStations = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE}/stations`);
|
const response = await fetch(`${API_BASE}/stations`, { credentials: 'include' });
|
||||||
|
if (response.status === 401) {
|
||||||
|
localStorage.clear();
|
||||||
|
window.location.href = 'index.html';
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!response.ok) throw new Error('Failed to fetch stations');
|
if (!response.ok) throw new Error('Failed to fetch stations');
|
||||||
|
|
||||||
const newStationList = await response.json();
|
const newStationList = await response.json();
|
||||||
|
|
||||||
// If the number of stations has changed, we must do a full re-render.
|
|
||||||
if (newStationList.length !== allStations.length) {
|
if (newStationList.length !== allStations.length) {
|
||||||
allStations = newStationList;
|
allStations = newStationList;
|
||||||
renderStations(allStations);
|
renderStations(allStations);
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, we can do a more efficient status-only update.
|
|
||||||
allStations = newStationList;
|
allStations = newStationList;
|
||||||
updateStationStatuses(allStations);
|
// A more efficient status-only update could go here later
|
||||||
fetchAndApplyStats(); // Fetch and update daily stats
|
renderStations(allStations); // Re-render to update statuses
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- THIS IS THE FIX ---
|
||||||
|
// Call this AFTER the if/else, so it always runs on a successful fetch.
|
||||||
|
fetchAndApplyStats();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
stationCountEl.textContent = 'Could not load stations. Is the backend running?';
|
if (stationCountEl) stationCountEl.textContent = 'Could not load stations. Is the backend running?';
|
||||||
if (pollingInterval) clearInterval(pollingInterval);
|
if (pollingInterval) clearInterval(pollingInterval);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue