Skip to content

Commit e7c463c

Browse files
authored
Merge pull request #62 from SenaxInc/copilot/add-github-action-html-render
Add CI workflow to render HTML from Arduino web servers and generate screenshots
2 parents 8eb62de + af88c88 commit e7c463c

File tree

9 files changed

+325
-0
lines changed

9 files changed

+325
-0
lines changed

.github/workflows/html-render.yml

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
name: HTML Render and Screenshot
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
paths:
7+
- 'TankAlarm-092025-Server-Hologram/*.ino'
8+
- 'TankAlarm-112025-Server-BluesOpta/*.ino'
9+
- '.github/workflows/html-render.yml'
10+
pull_request:
11+
branches: [main, master]
12+
paths:
13+
- 'TankAlarm-092025-Server-Hologram/*.ino'
14+
- 'TankAlarm-112025-Server-BluesOpta/*.ino'
15+
- '.github/workflows/html-render.yml'
16+
workflow_dispatch:
17+
18+
jobs:
19+
render-html:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: write
23+
steps:
24+
- name: Checkout repository
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Node.js
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: '20'
31+
32+
- name: Install Playwright
33+
run: |
34+
npm install playwright
35+
npx playwright install chromium
36+
37+
- name: Extract and render HTML for 092025 Server
38+
run: |
39+
mkdir -p /tmp/html-092025
40+
python3 << 'EOF'
41+
import re
42+
import os
43+
44+
ino_file = 'TankAlarm-092025-Server-Hologram/TankAlarm-092025-Server-Hologram.ino'
45+
output_dir = '/tmp/html-092025'
46+
47+
with open(ino_file, 'r', encoding='utf-8', errors='ignore') as f:
48+
content = f.read()
49+
50+
# Find functions that generate HTML pages
51+
pages = {
52+
'dashboard': ('sendWebPage', 'Dashboard'),
53+
'email-management': ('sendEmailManagementPage', 'Email Management'),
54+
'tank-management': ('sendTankManagementPage', 'Tank Management'),
55+
'calibration': ('sendCalibrationPage', 'Calibration'),
56+
'404': ('send404Page', '404 Page')
57+
}
58+
59+
for page_name, (func_name, title) in pages.items():
60+
# Find the function more carefully
61+
func_pattern = rf'void {func_name}\([^)]*\)\s*\{{'
62+
match = re.search(func_pattern, content)
63+
64+
if match:
65+
start_pos = match.end()
66+
# Find the matching closing brace
67+
brace_count = 1
68+
pos = start_pos
69+
while pos < len(content) and brace_count > 0:
70+
if content[pos] == '{':
71+
brace_count += 1
72+
elif content[pos] == '}':
73+
brace_count -= 1
74+
pos += 1
75+
76+
func_body = content[start_pos:pos-1]
77+
78+
# Extract HTML from client.println statements
79+
html_lines = []
80+
for line in func_body.split('\n'):
81+
line = line.strip()
82+
# Match client.println("...") or client.print("...")
83+
println_match = re.search(r'client\.print(?:ln)?\s*\(\s*[fF]?\s*\(\s*"([^"]*)"\s*\)\s*\)', line)
84+
if not println_match:
85+
println_match = re.search(r'client\.print(?:ln)?\s*\(\s*"([^"]*)"\s*\)', line)
86+
87+
if println_match:
88+
html_content = println_match.group(1)
89+
# Unescape the string
90+
html_content = html_content.replace(r'\"', '"')
91+
html_content = html_content.replace(r"\'", "'")
92+
html_content = html_content.replace(r'\n', '\n')
93+
html_lines.append(html_content)
94+
# Handle String concatenation with variables
95+
elif 'client.print' in line:
96+
# Check for meta refresh
97+
if 'webPageRefreshSeconds' in line:
98+
html_lines.append("<meta http-equiv='refresh' content='30'>")
99+
# For other dynamic content, extract what we can
100+
elif '"' in line:
101+
parts = re.findall(r'"([^"]*)"', line)
102+
for part in parts:
103+
html_lines.append(part)
104+
105+
# Join HTML lines
106+
html_content = '\n'.join(html_lines)
107+
108+
# Add sample data for dynamic content
109+
if page_name == 'dashboard':
110+
# Add sample tank data at the end before closing body tag
111+
sample_data = '''
112+
<div class="tank-container">
113+
<div class="tank-card">
114+
<div class="tank-header">Site A - Tank #1</div>
115+
<div class="tank-level">Level: 75%</div>
116+
<div class="tank-change positive">Change: +5%</div>
117+
<div class="status-normal">Status: Normal</div>
118+
</div>
119+
<div class="tank-card">
120+
<div class="tank-header">Site B - Tank #2</div>
121+
<div class="tank-level">Level: 30%</div>
122+
<div class="tank-change negative">Change: -10%</div>
123+
<div class="status-alarm">Status: LOW ALARM</div>
124+
</div>
125+
</div>
126+
<div class="footer">Last updated: 2025-11-12 18:00:00 UTC</div>
127+
</body>
128+
</html>
129+
'''
130+
# Insert before closing tags if they don't exist
131+
if '</body>' not in html_content:
132+
html_content += sample_data
133+
134+
# Make sure we have closing tags
135+
if '</html>' not in html_content:
136+
if '</body>' not in html_content:
137+
html_content += '\n</body>'
138+
html_content += '\n</html>'
139+
140+
# Write to file
141+
output_file = os.path.join(output_dir, f'{page_name}.html')
142+
with open(output_file, 'w', encoding='utf-8') as out:
143+
out.write(html_content)
144+
145+
print(f'Extracted {page_name}: {len(html_lines)} lines')
146+
EOF
147+
148+
- name: Extract and render HTML for 112025 Server
149+
run: |
150+
mkdir -p /tmp/html-112025
151+
python3 << 'EOF'
152+
import re
153+
154+
ino_file = 'TankAlarm-112025-Server-BluesOpta/TankAlarm-112025-Server-BluesOpta.ino'
155+
output_dir = '/tmp/html-112025'
156+
157+
with open(ino_file, 'r', encoding='utf-8', errors='ignore') as f:
158+
content = f.read()
159+
160+
# Find the DASHBOARD_HTML constant
161+
html_pattern = r'static const char DASHBOARD_HTML\[\] PROGMEM = R"HTML\((.*?)\)HTML"'
162+
match = re.search(html_pattern, content, re.DOTALL)
163+
164+
if match:
165+
html_content = match.group(1)
166+
167+
output_file = output_dir + '/dashboard.html'
168+
with open(output_file, 'w', encoding='utf-8') as out:
169+
out.write(html_content)
170+
171+
print(f'Extracted dashboard HTML: {len(html_content)} bytes')
172+
EOF
173+
174+
- name: Take screenshots with Playwright
175+
run: |
176+
node << 'EOF'
177+
const playwright = require('playwright');
178+
const fs = require('fs');
179+
const path = require('path');
180+
181+
async function takeScreenshots() {
182+
const browser = await playwright.chromium.launch();
183+
const context = await browser.newContext({
184+
viewport: { width: 1280, height: 720 }
185+
});
186+
const page = await context.newPage();
187+
188+
// Screenshot 092025 pages
189+
const dir092025 = '/tmp/html-092025';
190+
const output092025 = 'TankAlarm-092025-Server-Hologram/screenshots';
191+
192+
if (fs.existsSync(dir092025)) {
193+
fs.mkdirSync(output092025, { recursive: true });
194+
const files = fs.readdirSync(dir092025);
195+
196+
for (const file of files) {
197+
if (file.endsWith('.html')) {
198+
const htmlPath = path.join(dir092025, file);
199+
const screenshotName = file.replace('.html', '.png');
200+
const screenshotPath = path.join(output092025, screenshotName);
201+
202+
try {
203+
await page.goto('file://' + htmlPath, { waitUntil: 'networkidle' });
204+
await page.screenshot({ path: screenshotPath, fullPage: true });
205+
console.log(`Screenshot saved: ${screenshotPath}`);
206+
} catch (error) {
207+
console.error(`Error capturing ${file}:`, error.message);
208+
}
209+
}
210+
}
211+
}
212+
213+
// Screenshot 112025 pages
214+
const dir112025 = '/tmp/html-112025';
215+
const output112025 = 'TankAlarm-112025-Server-BluesOpta/screenshots';
216+
217+
if (fs.existsSync(dir112025)) {
218+
fs.mkdirSync(output112025, { recursive: true });
219+
const files = fs.readdirSync(dir112025);
220+
221+
for (const file of files) {
222+
if (file.endsWith('.html')) {
223+
const htmlPath = path.join(dir112025, file);
224+
const screenshotName = file.replace('.html', '.png');
225+
const screenshotPath = path.join(output112025, screenshotName);
226+
227+
try {
228+
await page.goto('file://' + htmlPath, { waitUntil: 'networkidle' });
229+
await page.screenshot({ path: screenshotPath, fullPage: true });
230+
console.log(`Screenshot saved: ${screenshotPath}`);
231+
} catch (error) {
232+
console.error(`Error capturing ${file}:`, error.message);
233+
}
234+
}
235+
}
236+
}
237+
238+
await browser.close();
239+
}
240+
241+
takeScreenshots().catch(console.error);
242+
EOF
243+
244+
- name: Generate WEBSITE_PREVIEW.md for 092025
245+
run: |
246+
cat > TankAlarm-092025-Server-Hologram/WEBSITE_PREVIEW.md << 'EOF'
247+
# Website Preview - TankAlarm 092025 Server
248+
249+
This document contains screenshots of the web interface served by the TankAlarm-092025-Server-Hologram.
250+
251+
## Dashboard
252+
![Dashboard](screenshots/dashboard.png)
253+
254+
## Email Management
255+
![Email Management](screenshots/email-management.png)
256+
257+
## Tank Management
258+
![Tank Management](screenshots/tank-management.png)
259+
260+
## Calibration
261+
![Calibration](screenshots/calibration.png)
262+
263+
## 404 Page
264+
![404 Page](screenshots/404.png)
265+
266+
---
267+
*Screenshots automatically generated by GitHub Actions*
268+
*Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
269+
EOF
270+
271+
- name: Generate WEBSITE_PREVIEW.md for 112025
272+
run: |
273+
cat > TankAlarm-112025-Server-BluesOpta/WEBSITE_PREVIEW.md << 'EOF'
274+
# Website Preview - TankAlarm 112025 Server
275+
276+
This document contains screenshots of the web interface served by the TankAlarm-112025-Server-BluesOpta.
277+
278+
## Dashboard
279+
![Dashboard](screenshots/dashboard.png)
280+
281+
---
282+
*Screenshots automatically generated by GitHub Actions*
283+
*Last updated: $(date -u +"%Y-%m-%d %H:%M:%S UTC")*
284+
EOF
285+
286+
- name: Commit and push changes
287+
run: |
288+
git config --local user.email "github-actions[bot]@users.noreply.github.com"
289+
git config --local user.name "github-actions[bot]"
290+
git add TankAlarm-092025-Server-Hologram/WEBSITE_PREVIEW.md TankAlarm-092025-Server-Hologram/screenshots/
291+
git add TankAlarm-112025-Server-BluesOpta/WEBSITE_PREVIEW.md TankAlarm-112025-Server-BluesOpta/screenshots/
292+
git diff --staged --quiet || git commit -m "Update website preview screenshots [skip ci]"
293+
git push
294+
env:
295+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Website Preview - TankAlarm 092025 Server
2+
3+
This document contains screenshots of the web interface served by the TankAlarm-092025-Server-Hologram.
4+
5+
## Dashboard
6+
![Dashboard](screenshots/dashboard.png)
7+
8+
## Email Management
9+
![Email Management](screenshots/email-management.png)
10+
11+
## Tank Management
12+
![Tank Management](screenshots/tank-management.png)
13+
14+
## Calibration
15+
![Calibration](screenshots/calibration.png)
16+
17+
## 404 Page
18+
![404 Page](screenshots/404.png)
19+
20+
---
21+
*Screenshots automatically generated by GitHub Actions*
10.9 KB
Loading
64.8 KB
Loading
67.1 KB
Loading
47.9 KB
Loading
44.4 KB
Loading
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Website Preview - TankAlarm 112025 Server
2+
3+
This document contains screenshots of the web interface served by the TankAlarm-112025-Server-BluesOpta.
4+
5+
## Dashboard
6+
![Dashboard](screenshots/dashboard.png)
7+
8+
---
9+
*Screenshots automatically generated by GitHub Actions*
79.6 KB
Loading

0 commit comments

Comments
 (0)