Skip to content

Commit b4c79b9

Browse files
itsDNNSclaude
andcommitted
Add calendar navigation, hamburger menu, and trend charts
- Hamburger sliding sidebar with Live/Day/Week/Month navigation - Calendar popup for date selection with highlighted data days - Trend views with Chart.js (DS Power, DS SNR, US Power, Errors) - New API endpoints: /api/calendar, /api/trends, /api/snapshot/daily - Storage methods for intraday, daily, and date-range queries - Configurable snapshot_time for daily reference point Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b3e73b3 commit b4c79b9

File tree

6 files changed

+791
-203
lines changed

6 files changed

+791
-203
lines changed

app/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"poll_interval": 300,
2828
"web_port": 8765,
2929
"history_days": 7,
30+
"snapshot_time": "06:00",
3031
"theme": "dark",
3132
}
3233

app/storage.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,69 @@ def get_snapshot(self, timestamp):
7777
"us_channels": json.loads(row[2]),
7878
}
7979

80+
def get_dates_with_data(self):
81+
"""Return list of dates (YYYY-MM-DD) that have at least one snapshot."""
82+
with sqlite3.connect(self.db_path) as conn:
83+
rows = conn.execute(
84+
"SELECT DISTINCT substr(timestamp, 1, 10) as day FROM snapshots ORDER BY day"
85+
).fetchall()
86+
return [r[0] for r in rows]
87+
88+
def get_daily_snapshot(self, date, target_time="06:00"):
89+
"""Get the snapshot closest to target_time on the given date."""
90+
target_ts = f"{date}T{target_time}:00"
91+
with sqlite3.connect(self.db_path) as conn:
92+
row = conn.execute(
93+
"""SELECT timestamp, summary_json, ds_channels_json, us_channels_json
94+
FROM snapshots
95+
WHERE timestamp LIKE ?
96+
ORDER BY ABS(julianday(timestamp) - julianday(?))
97+
LIMIT 1""",
98+
(f"{date}%", target_ts),
99+
).fetchone()
100+
if not row:
101+
return None
102+
return {
103+
"timestamp": row[0],
104+
"summary": json.loads(row[1]),
105+
"ds_channels": json.loads(row[2]),
106+
"us_channels": json.loads(row[3]),
107+
}
108+
109+
def get_trend_data(self, start_date, end_date, target_time="06:00"):
110+
"""Get summary data points for a date range, one per day (closest to target_time).
111+
Returns list of {date, timestamp, ...summary_fields}."""
112+
dates = []
113+
with sqlite3.connect(self.db_path) as conn:
114+
rows = conn.execute(
115+
"SELECT DISTINCT substr(timestamp, 1, 10) as day FROM snapshots WHERE day >= ? AND day <= ? ORDER BY day",
116+
(start_date, end_date),
117+
).fetchall()
118+
dates = [r[0] for r in rows]
119+
120+
results = []
121+
for date in dates:
122+
snap = self.get_daily_snapshot(date, target_time)
123+
if snap:
124+
entry = {"date": date, "timestamp": snap["timestamp"]}
125+
entry.update(snap["summary"])
126+
results.append(entry)
127+
return results
128+
129+
def get_intraday_data(self, date):
130+
"""Get all snapshots for a single day (for day-detail trends)."""
131+
with sqlite3.connect(self.db_path) as conn:
132+
rows = conn.execute(
133+
"SELECT timestamp, summary_json FROM snapshots WHERE timestamp LIKE ? ORDER BY timestamp",
134+
(f"{date}%",),
135+
).fetchall()
136+
results = []
137+
for row in rows:
138+
entry = {"timestamp": row[0]}
139+
entry.update(json.loads(row[1]))
140+
results.append(entry)
141+
return results
142+
80143
def _cleanup(self):
81144
"""Delete snapshots older than max_days."""
82145
cutoff = (datetime.now() - timedelta(days=self.max_days)).strftime(

0 commit comments

Comments
 (0)