@@ -112,29 +112,62 @@ jobs:
112112
113113 # Add sample data for dynamic content
114114 if page_name == 'dashboard':
115- # Add sample tank data at the end before closing body tag
115+ # Add sample tank data grouped by sites
116116 sample_data = '''
117+ <h2>North Facility</h2>
117118 <div class="tank-container">
118119 <div class="tank-card">
119- <div class="tank-header">Site A - Tank #1</div>
120- <div class="tank-level">Level: 75%</div>
121- <div class="tank-change positive">Change: +5%</div>
122- <div class="status-normal">Status: Normal</div>
120+ <div class="tank-header">Tank #1</div>
121+ <div class="tank-level">Level: 85.3%</div>
122+ <div class="tank-change positive">24hr Change: +2.1%</div>
123+ <div class="status status-normal">Status: Normal</div>
124+ <div style="font-size: 12px; color: #666; margin-top: 10px;">Updated: 11/22/2025, 4:55:56 AM</div>
123125 </div>
124126 <div class="tank-card">
125- <div class="tank-header">Site B - Tank #2</div>
126- <div class="tank-level">Level: 30%</div>
127- <div class="tank-change negative">Change: -10%</div>
128- <div class="status-alarm">Status: LOW ALARM</div>
127+ <div class="tank-header">Tank #2</div>
128+ <div class="tank-level">Level: 52.1%</div>
129+ <div class="tank-change negative">24hr Change: -3.2%</div>
130+ <div class="status status-normal">Status: Normal</div>
131+ <div style="font-size: 12px; color: #666; margin-top: 10px;">Updated: 11/22/2025, 4:55:56 AM</div>
129132 </div>
130133 </div>
131- <div class="footer">Last updated: 2025-11-12 18:00:00 UTC</div>
132- </body>
133- </html>
134+ <h2>South Facility</h2>
135+ <div class="tank-container">
136+ <div class="tank-card">
137+ <div class="tank-header">Tank #1</div>
138+ <div class="tank-level">Level: 22.5%</div>
139+ <div class="tank-change negative">24hr Change: -8.5%</div>
140+ <div class="status status-alarm">Status: LOW</div>
141+ <div style="font-size: 12px; color: #666; margin-top: 10px;">Updated: 11/22/2025, 4:54:56 AM</div>
142+ </div>
143+ </div>
144+ <h2>East Facility</h2>
145+ <div class="tank-container">
146+ <div class="tank-card">
147+ <div class="tank-header">Tank #1</div>
148+ <div class="tank-level">Level: 95.8%</div>
149+ <div class="tank-change positive">24hr Change: +1.2%</div>
150+ <div class="status status-normal">Status: Normal</div>
151+ <div style="font-size: 12px; color: #666; margin-top: 10px;">Updated: 11/22/2025, 4:56:26 AM</div>
152+ </div>
153+ </div>
154+ <h2>West Facility</h2>
155+ <div class="tank-container">
156+ <div class="tank-card">
157+ <div class="tank-header">Tank #1</div>
158+ <div class="tank-level">Level: 15.2%</div>
159+ <div class="tank-change negative">24hr Change: -12.8%</div>
160+ <div class="status status-alarm">Status: CRITICAL</div>
161+ <div style="font-size: 12px; color: #666; margin-top: 10px;">Updated: 11/22/2025, 4:57:26 AM</div>
162+ </div>
163+ </div>
164+ <div class="footer">Last updated: 2025-11-22 18:00:00 UTC</div>
134165 '''
135- # Insert before closing tags if they don't exist
136- if '</body>' not in html_content:
137- html_content += sample_data
166+ # Replace the "No tank reports received yet" message with sample data
167+ html_content = html_content.replace(
168+ "<p style='text-align: center;'>No tank reports received yet.</p>",
169+ sample_data
170+ )
138171
139172 # Make sure we have closing tags
140173 if '</html>' not in html_content:
@@ -155,6 +188,8 @@ jobs:
155188 mkdir -p /tmp/html-112025
156189 python3 << 'EOF'
157190 import re
191+ import json
192+ import time
158193
159194 ino_file = 'TankAlarm-112025-Server-BluesOpta/TankAlarm-112025-Server-BluesOpta.ino'
160195 output_dir = '/tmp/html-112025'
@@ -169,11 +204,107 @@ jobs:
169204 if match:
170205 html_content = match.group(1)
171206
207+ # Create sample data for the server dashboard
208+ current_time = int(time.time())
209+ sample_data = {
210+ "serverUid": "dev:server001",
211+ "server": {
212+ "name": "Demo Tank Alarm Server",
213+ "clientFleet": "demo-tankalarm-clients",
214+ "webRefreshSeconds": 60,
215+ "pinConfigured": True
216+ },
217+ "nextDailyEmailEpoch": current_time + 43200, # 12 hours from now
218+ "lastSyncEpoch": current_time - 180, # 3 minutes ago
219+ "clients": [
220+ {
221+ "uid": "dev:client001",
222+ "site": "North Facility",
223+ "lastUpdate": current_time - 180,
224+ "tanks": [
225+ {
226+ "tank": 1,
227+ "label": "Primary",
228+ "levelInches": 78.5,
229+ "percent": 85.3,
230+ "alarm": False,
231+ "lastUpdate": current_time - 180
232+ },
233+ {
234+ "tank": 2,
235+ "label": "Secondary",
236+ "levelInches": 45.2,
237+ "percent": 52.1,
238+ "alarm": False,
239+ "lastUpdate": current_time - 180
240+ }
241+ ]
242+ },
243+ {
244+ "uid": "dev:client002",
245+ "site": "South Facility",
246+ "lastUpdate": current_time - 240,
247+ "tanks": [
248+ {
249+ "tank": 1,
250+ "label": "Storage",
251+ "levelInches": 18.3,
252+ "percent": 22.5,
253+ "alarm": True,
254+ "alarmType": "LOW",
255+ "lastUpdate": current_time - 240
256+ }
257+ ]
258+ },
259+ {
260+ "uid": "dev:client003",
261+ "site": "East Facility",
262+ "lastUpdate": current_time - 150,
263+ "tanks": [
264+ {
265+ "tank": 1,
266+ "label": "Main",
267+ "levelInches": 92.1,
268+ "percent": 95.8,
269+ "alarm": False,
270+ "lastUpdate": current_time - 150
271+ }
272+ ]
273+ },
274+ {
275+ "uid": "dev:client004",
276+ "site": "West Facility",
277+ "lastUpdate": current_time - 90,
278+ "tanks": [
279+ {
280+ "tank": 1,
281+ "label": "Backup",
282+ "levelInches": 12.7,
283+ "percent": 15.2,
284+ "alarm": True,
285+ "alarmType": "CRITICAL",
286+ "lastUpdate": current_time - 90
287+ }
288+ ]
289+ }
290+ ]
291+ }
292+
293+ # Inject the sample data
294+ sample_data_json = json.dumps(sample_data)
295+
296+ # Replace the refreshData() call with data injection
297+ html_content = html_content.replace(
298+ 'refreshData();',
299+ f'applyServerData({sample_data_json}, null);'
300+ )
301+
172302 output_file = output_dir + '/dashboard.html'
173303 with open(output_file, 'w', encoding='utf-8') as out:
174304 out.write(html_content)
175305
176306 print(f'Extracted dashboard HTML: {len(html_content)} bytes')
307+ print(f'Injected sample data with {len(sample_data["clients"])} clients')
177308
178309 # Find the CONFIG_GENERATOR_HTML constant
179310 config_pattern = r'static const char CONFIG_GENERATOR_HTML\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
@@ -194,28 +325,151 @@ jobs:
194325 mkdir -p /tmp/html-112025-viewer
195326 python3 << 'EOF'
196327 import re
328+ import json
329+ import time
197330
198331 ino_file = 'TankAlarm-112025-Viewer-BluesOpta/TankAlarm-112025-Viewer-BluesOpta.ino'
199332 output_dir = '/tmp/html-112025-viewer'
200333
201334 with open(ino_file, 'r', encoding='utf-8', errors='ignore') as f:
202335 content = f.read()
203336
204- # Find the VIEWER_DASHBOARD_HTML constant
205- html_pattern = r'static const char VIEWER_DASHBOARD_HTML\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
206- match = re.search(html_pattern, content, re.DOTALL)
337+ # Find the VIEWER_DASHBOARD_HTML constant - need to handle macro splits
338+ # The HTML is split into multiple R"HTML()HTML" sections with STR() macros in between
339+ start_pattern = r'static const char VIEWER_DASHBOARD_HTML\[\] PROGMEM = R"HTML\('
340+ start_match = re.search(start_pattern, content)
207341
208- if match:
209- html_content = match.group(1)
342+ if start_match:
343+ # Find the end - look for the final )HTML"; that ends the declaration
344+ start_pos = start_match.end()
345+ # Find the position after the HTML definition ends
346+ end_pattern = r'\)HTML";'
347+ remaining = content[start_pos:]
348+
349+ # Collect all parts, handling the macro splits
350+ html_parts = []
351+ current_pos = 0
352+
353+ while True:
354+ # Find next )HTML"
355+ end_match = re.search(r'\)HTML"', remaining[current_pos:])
356+ if not end_match:
357+ break
358+
359+ # Extract the HTML part
360+ html_parts.append(remaining[current_pos:current_pos + end_match.start()])
361+
362+ # Check if this is the final terminator
363+ check_pos = current_pos + end_match.end()
364+ if check_pos < len(remaining) and remaining[check_pos:check_pos+1] == ';':
365+ # This is the final terminator
366+ break
367+
368+ # Look for the next R"HTML(
369+ next_start = re.search(r'R"HTML\(', remaining[check_pos:])
370+ if next_start:
371+ # Extract the macro value in between
372+ macro_section = remaining[check_pos:check_pos + next_start.start()]
373+ # Replace STR(WEB_REFRESH_SECONDS) with 30
374+ if 'STR(WEB_REFRESH_SECONDS)' in macro_section:
375+ html_parts.append('30')
376+ current_pos = check_pos + next_start.end()
377+ else:
378+ break
379+
380+ html_content = ''.join(html_parts)
381+
382+ # Create sample data for the viewer
383+ current_time = int(time.time())
384+ sample_data = {
385+ "viewerName": "Demo Tank Alarm Viewer",
386+ "viewerUid": "dev:viewer001",
387+ "sourceServerName": "Demo Server",
388+ "sourceServerUid": "dev:server001",
389+ "generatedEpoch": current_time - 300, # 5 minutes ago
390+ "lastFetchEpoch": current_time - 120, # 2 minutes ago
391+ "nextFetchEpoch": current_time + 21480, # ~6 hours from now
392+ "refreshSeconds": 21600, # 6 hours
393+ "baseHour": 6,
394+ "tanks": [
395+ {
396+ "client": "dev:client001",
397+ "site": "North Facility",
398+ "label": "Primary",
399+ "tank": 1,
400+ "levelInches": 78.5,
401+ "percent": 85.3,
402+ "alarm": False,
403+ "lastUpdate": current_time - 180
404+ },
405+ {
406+ "client": "dev:client001",
407+ "site": "North Facility",
408+ "label": "Secondary",
409+ "tank": 2,
410+ "levelInches": 45.2,
411+ "percent": 52.1,
412+ "alarm": False,
413+ "lastUpdate": current_time - 180
414+ },
415+ {
416+ "client": "dev:client002",
417+ "site": "South Facility",
418+ "label": "Storage",
419+ "tank": 1,
420+ "levelInches": 18.3,
421+ "percent": 22.5,
422+ "alarm": True,
423+ "alarmType": "LOW",
424+ "lastUpdate": current_time - 240
425+ },
426+ {
427+ "client": "dev:client003",
428+ "site": "East Facility",
429+ "label": "Main",
430+ "tank": 1,
431+ "levelInches": 92.1,
432+ "percent": 95.8,
433+ "alarm": False,
434+ "lastUpdate": current_time - 150
435+ },
436+ {
437+ "client": "dev:client004",
438+ "site": "West Facility",
439+ "label": "Backup",
440+ "tank": 1,
441+ "levelInches": 12.7,
442+ "percent": 15.2,
443+ "alarm": True,
444+ "alarmType": "CRITICAL",
445+ "lastUpdate": current_time - 90
446+ }
447+ ]
448+ }
449+
450+ # Inject the sample data into the HTML by replacing the fetchTanks call
451+ # Find the fetchTanks() call at the end of the script and replace it with data injection
452+ sample_data_json = json.dumps(sample_data)
453+
454+ # Replace the async fetchTanks call with synchronous data application
455+ html_content = html_content.replace(
456+ 'fetchTanks();',
457+ f'applyTankData({sample_data_json}, null);'
458+ )
210459
211- # Replace the STR(WEB_REFRESH_SECONDS) macro with a placeholder value
212- html_content = re.sub(r'\)HTML" STR\(WEB_REFRESH_SECONDS\) R"HTML\(', '30', html_content)
460+ # Also remove the setInterval call to prevent attempts to fetch from non-existent API
461+ html_content = re.sub(
462+ r'setInterval\(\(\) => fetchTanks\(state\.selected\), REFRESH_SECONDS \* 1000\);',
463+ '// Auto-refresh disabled for static preview',
464+ html_content
465+ )
213466
214467 output_file = output_dir + '/dashboard.html'
215468 with open(output_file, 'w', encoding='utf-8') as out:
216469 out.write(html_content)
217470
218471 print(f'Extracted viewer dashboard HTML: {len(html_content)} bytes')
472+ print(f'Injected sample data with {len(sample_data["tanks"])} tanks')
219473 EOF
220474
221475 - name : Take screenshots with Playwright
0 commit comments