Skip to content

Commit deecf08

Browse files
authored
Merge pull request #140 from SenaxInc/copilot/clean-up-web-page-format
Minify 112025 web pages for reduced Arduino Opta resource usage
2 parents a38ffc6 + a7b12e1 commit deecf08

File tree

5 files changed

+226
-4722
lines changed

5 files changed

+226
-4722
lines changed

.github/workflows/html-render.yml

Lines changed: 191 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ on:
1515
- 'TankAlarm-112025-Server-BluesOpta/*.ino'
1616
- 'TankAlarm-112025-Viewer-BluesOpta/*.ino'
1717
- '.github/workflows/html-render.yml'
18+
types: [opened, synchronize, reopened, review_requested]
1819
workflow_dispatch:
1920

2021
jobs:
2122
render-html:
2223
runs-on: ubuntu-latest
2324
permissions:
2425
contents: write
26+
pull-requests: write
2527
steps:
2628
- name: Checkout repository
2729
uses: actions/checkout@v4
@@ -197,127 +199,96 @@ jobs:
197199
with open(ino_file, 'r', encoding='utf-8', errors='ignore') as f:
198200
content = f.read()
199201
200-
# Find the DASHBOARD_HTML constant
201-
html_pattern = r'static const char DASHBOARD_HTML\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
202-
match = re.search(html_pattern, content, re.DOTALL)
202+
current_time = int(time.time())
203203
204-
if match:
205-
html_content = match.group(1)
204+
# Sample data for dashboard and client console
205+
sample_clients_data = {
206+
"si": "dev:server001",
207+
"srv": {
208+
"n": "Demo Tank Alarm Server",
209+
"cf": "demo-tankalarm-clients",
210+
"ps": False,
211+
"pc": True
212+
},
213+
"nde": current_time + 43200,
214+
"lse": current_time - 180,
215+
"cs": [
216+
{"c": "dev:client001", "s": "North Facility", "n": "Primary", "k": "1", "l": 78.5, "p": 85.3, "a": False, "u": current_time - 180, "v": 12.4},
217+
{"c": "dev:client001", "s": "North Facility", "n": "Secondary", "k": "2", "l": 45.2, "p": 52.1, "a": False, "u": current_time - 180, "v": 12.4},
218+
{"c": "dev:client002", "s": "South Facility", "n": "Storage", "k": "1", "l": 18.3, "p": 22.5, "a": True, "aType": "LOW", "u": current_time - 240, "v": 11.8},
219+
{"c": "dev:client003", "s": "East Facility", "n": "Main", "k": "1", "l": 92.1, "p": 95.8, "a": False, "u": current_time - 150, "v": 12.6},
220+
{"c": "dev:client004", "s": "West Facility", "n": "Backup", "k": "1", "l": 12.7, "p": 15.2, "a": True, "aType": "CRITICAL", "u": current_time - 90, "v": 10.2}
221+
]
222+
}
223+
224+
# Extract all HTML pages
225+
html_pages = {
226+
'DASHBOARD_HTML': 'dashboard',
227+
'CONFIG_GENERATOR_HTML': 'config-generator',
228+
'SERIAL_MONITOR_HTML': 'serial-monitor',
229+
'CALIBRATION_HTML': 'calibration',
230+
'CONTACTS_MANAGER_HTML': 'contacts',
231+
'CLIENT_CONSOLE_HTML': 'client-console'
232+
}
233+
234+
for const_name, file_name in html_pages.items():
235+
pattern = rf'static const char {const_name}\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
236+
match = re.search(pattern, content, re.DOTALL)
206237
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-
}
238+
if match:
239+
html_content = match.group(1)
240+
241+
# For dashboard, inject sample data
242+
if const_name == 'DASHBOARD_HTML':
243+
sample_json = json.dumps(sample_clients_data)
244+
html_content = html_content.replace('refreshData();', f'applyServerData({sample_json});')
245+
246+
# For client console, inject sample data
247+
elif const_name == 'CLIENT_CONSOLE_HTML':
248+
sample_json = json.dumps(sample_clients_data)
249+
html_content = html_content.replace('refreshData();', f'applyServerData({sample_json}, "dev:client001");')
250+
251+
# For serial monitor, inject sample log data
252+
elif const_name == 'SERIAL_MONITOR_HTML':
253+
sample_logs = {
254+
"logs": [
255+
{"timestamp": current_time - 60, "level": "INFO", "source": "server", "message": "Server initialized successfully"},
256+
{"timestamp": current_time - 45, "level": "INFO", "source": "server", "message": "Connected to Notehub"},
257+
{"timestamp": current_time - 30, "level": "DEBUG", "source": "server", "message": "Polling for client updates..."},
258+
{"timestamp": current_time - 15, "level": "INFO", "source": "server", "message": "Received telemetry from dev:client001"}
272259
]
273-
},
274-
{
275-
"uid": "dev:client004",
276-
"site": "West Facility",
277-
"lastUpdate": current_time - 90,
260+
}
261+
sample_json = json.dumps(sample_logs)
262+
html_content = html_content.replace('refreshServerLogs();', f'renderLogs(els.serverLogs, {sample_json}.logs);')
263+
264+
# For calibration, inject sample calibration data
265+
elif const_name == 'CALIBRATION_HTML':
266+
sample_cal = {
267+
"calibrations": [
268+
{"clientUid": "dev:client001", "tankNumber": 1, "entryCount": 5, "hasLearnedCalibration": True, "rSquared": 0.987, "learnedSlope": 7.42, "originalMaxValue": 120, "lastCalibrationEpoch": current_time - 86400},
269+
{"clientUid": "dev:client002", "tankNumber": 1, "entryCount": 2, "hasLearnedCalibration": False, "rSquared": 0, "learnedSlope": 0, "originalMaxValue": 96, "lastCalibrationEpoch": current_time - 172800}
270+
],
271+
"logs": []
272+
}
273+
sample_tanks = {
278274
"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-
}
275+
{"client": "dev:client001", "tank": 1, "site": "North Facility", "label": "Primary", "heightInches": 120, "levelInches": 78.5, "sensorMa": 14.2},
276+
{"client": "dev:client002", "tank": 1, "site": "South Facility", "label": "Storage", "heightInches": 96, "levelInches": 18.3, "sensorMa": 6.8}
288277
]
289278
}
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-
302-
output_file = output_dir + '/dashboard.html'
303-
with open(output_file, 'w', encoding='utf-8') as out:
304-
out.write(html_content)
305-
306-
print(f'Extracted dashboard HTML: {len(html_content)} bytes')
307-
print(f'Injected sample data with {len(sample_data["clients"])} clients')
308-
309-
# Find the CONFIG_GENERATOR_HTML constant
310-
config_pattern = r'static const char CONFIG_GENERATOR_HTML\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
311-
config_match = re.search(config_pattern, content, re.DOTALL)
312-
313-
if config_match:
314-
config_html = config_match.group(1)
315-
316-
config_file = output_dir + '/config-generator.html'
317-
with open(config_file, 'w', encoding='utf-8') as out:
318-
out.write(config_html)
319-
320-
print(f'Extracted config generator HTML: {len(config_html)} bytes')
279+
html_content = html_content.replace('loadTanks();', f'tanks = {json.dumps(sample_tanks["tanks"])}; populateTankDropdowns();')
280+
html_content = html_content.replace('loadCalibrationData();', f'calibrations = {json.dumps(sample_cal["calibrations"])}; calibrationLogs = []; updateCalibrationStats(); updateCalibrationTable(); updateCalibrationLog();')
281+
282+
# For contacts, inject sample contact data
283+
elif const_name == 'CONTACTS_MANAGER_HTML':
284+
# Just render the page without API calls
285+
html_content = html_content.replace('loadData();', '// Sample data mode - loadData() disabled for static preview')
286+
287+
output_file = f'{output_dir}/{file_name}.html'
288+
with open(output_file, 'w', encoding='utf-8') as out:
289+
out.write(html_content)
290+
291+
print(f'Extracted {file_name} HTML: {len(html_content)} bytes')
321292
EOF
322293
323294
- name: Extract and render HTML for 112025 Viewer
@@ -602,11 +573,35 @@ jobs:
602573
This document contains screenshots of the web interface served by the TankAlarm-112025-Server-BluesOpta.
603574
604575
## Dashboard
576+
Main fleet telemetry dashboard showing server metadata, statistics, and fleet telemetry table with relay controls.
577+
605578
![Dashboard](screenshots/dashboard.png)
606579
580+
## Client Console
581+
Configuration management interface for remote clients with PIN-protected controls.
582+
583+
![Client Console](screenshots/client-console.png)
584+
607585
## Config Generator
586+
Create new client configurations with sensor definitions and alarm thresholds.
587+
608588
![Config Generator](screenshots/config-generator.png)
609589
590+
## Serial Monitor
591+
Debug log viewer with server and client serial output.
592+
593+
![Serial Monitor](screenshots/serial-monitor.png)
594+
595+
## Calibration
596+
Calibration learning system with manual level reading submission and drift analysis.
597+
598+
![Calibration](screenshots/calibration.png)
599+
600+
## Contacts Manager
601+
Contact and notification management with alarm associations.
602+
603+
![Contacts Manager](screenshots/contacts.png)
604+
610605
---
611606
*Screenshots automatically generated by GitHub Actions*
612607
*Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
@@ -620,13 +615,94 @@ jobs:
620615
This document contains screenshots of the web interface served by the TankAlarm-112025-Viewer-BluesOpta.
621616
622617
## Dashboard
618+
Read-only fleet telemetry dashboard showing server metadata and fleet snapshot with tank levels.
619+
623620
![Dashboard](screenshots/dashboard.png)
624621
625622
---
626623
*Screenshots automatically generated by GitHub Actions*
627624
*Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
628625
EOF
629626
627+
- name: Upload screenshots as artifacts
628+
if: github.event_name == 'pull_request'
629+
uses: actions/upload-artifact@v4
630+
with:
631+
name: website-screenshots
632+
path: |
633+
TankAlarm-092025-Server-Hologram/screenshots/
634+
TankAlarm-112025-Server-BluesOpta/screenshots/
635+
TankAlarm-112025-Viewer-BluesOpta/screenshots/
636+
retention-days: 7
637+
638+
- name: Post preview comment on PR
639+
if: github.event_name == 'pull_request'
640+
uses: actions/github-script@v7
641+
with:
642+
script: |
643+
const fs = require('fs');
644+
const path = require('path');
645+
646+
// Check which screenshots were generated
647+
const screenshotDirs = [
648+
{ dir: 'TankAlarm-112025-Server-BluesOpta/screenshots', name: '112025 Server' },
649+
{ dir: 'TankAlarm-112025-Viewer-BluesOpta/screenshots', name: '112025 Viewer' },
650+
{ dir: 'TankAlarm-092025-Server-Hologram/screenshots', name: '092025 Server' }
651+
];
652+
653+
let commentBody = '## 🖼️ Website Preview\n\n';
654+
commentBody += 'Screenshots have been generated for the web pages modified in this PR.\n\n';
655+
commentBody += '> **Note:** Download the `website-screenshots` artifact to view the full-resolution images.\n\n';
656+
657+
for (const { dir, name } of screenshotDirs) {
658+
if (fs.existsSync(dir)) {
659+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.png'));
660+
if (files.length > 0) {
661+
commentBody += `### ${name}\n\n`;
662+
commentBody += '| Page | Screenshot |\n';
663+
commentBody += '|------|------------|\n';
664+
for (const file of files.sort()) {
665+
const pageName = file.replace('.png', '').replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
666+
commentBody += `| ${pageName} | ✅ Generated |\n`;
667+
}
668+
commentBody += '\n';
669+
}
670+
}
671+
}
672+
673+
commentBody += '---\n';
674+
commentBody += `*Generated by GitHub Actions on ${new Date().toISOString()}*`;
675+
676+
// Find existing comment to update or create new one
677+
const { data: comments } = await github.rest.issues.listComments({
678+
owner: context.repo.owner,
679+
repo: context.repo.repo,
680+
issue_number: context.issue.number
681+
});
682+
683+
const botComment = comments.find(comment =>
684+
comment.user.type === 'Bot' &&
685+
comment.body.includes('## 🖼️ Website Preview')
686+
);
687+
688+
if (botComment) {
689+
await github.rest.issues.updateComment({
690+
owner: context.repo.owner,
691+
repo: context.repo.repo,
692+
comment_id: botComment.id,
693+
body: commentBody
694+
});
695+
console.log('Updated existing preview comment');
696+
} else {
697+
await github.rest.issues.createComment({
698+
owner: context.repo.owner,
699+
repo: context.repo.repo,
700+
issue_number: context.issue.number,
701+
body: commentBody
702+
});
703+
console.log('Created new preview comment');
704+
}
705+
630706
- name: Commit and push changes
631707
if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
632708
run: |

0 commit comments

Comments
 (0)