upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/main/captive_portal.c
diff options
context:
space:
mode:
Diffstat (limited to 'main/captive_portal.c')
-rw-r--r--main/captive_portal.c69
1 files changed, 68 insertions, 1 deletions
diff --git a/main/captive_portal.c b/main/captive_portal.c
index 1a3d5ce..ea83906 100644
--- a/main/captive_portal.c
+++ b/main/captive_portal.c
@@ -2,6 +2,8 @@
2#include "firewall.h" 2#include "firewall.h"
3#include "session.h" 3#include "session.h"
4#include "config.h" 4#include "config.h"
5#include "mining_payment.h"
6#include "stratum_proxy.h"
5#include "esp_log.h" 7#include "esp_log.h"
6#include "esp_wifi.h" 8#include "esp_wifi.h"
7#include "cJSON.h" 9#include "cJSON.h"
@@ -31,6 +33,12 @@ static const char PORTAL_HTML_TEMPLATE[] = \
31"max-width:400px;width:100%;text-align:center}" 33"max-width:400px;width:100%;text-align:center}"
32"h1{font-size:28px;margin-bottom:8px;color:#f7931a}" 34"h1{font-size:28px;margin-bottom:8px;color:#f7931a}"
33".subtitle{color:#888;margin-bottom:24px;font-size:14px}" 35".subtitle{color:#888;margin-bottom:24px;font-size:14px}"
36".tabs{display:flex;gap:4px;margin-bottom:20px}"
37".tab{flex:1;padding:10px;border:none;border-radius:8px;background:#252525;color:#888;"
38"cursor:pointer;font-size:13px;font-weight:bold}"
39".tab.active{background:#f7931a;color:#000}"
40".tab-content{display:none}"
41".tab-content.active{display:block}"
34".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}" 42".price{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px}"
35".price-amount{font-size:36px;font-weight:bold;color:#f7931a}" 43".price-amount{font-size:36px;font-weight:bold;color:#f7931a}"
36".price-unit{color:#888;font-size:14px}" 44".price-unit{color:#888;font-size:14px}"
@@ -46,15 +54,22 @@ static const char PORTAL_HTML_TEMPLATE[] = \
46"background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}" 54"background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}"
47".mint-url:active{opacity:0.7}" 55".mint-url:active{opacity:0.7}"
48".mint-hint{color:#666;font-size:10px;margin-top:4px}" 56".mint-hint{color:#666;font-size:10px;margin-top:4px}"
57".mining-stats{background:#252525;border-radius:12px;padding:16px;margin-bottom:16px;text-align:left}"
58".mining-stat{display:flex;justify-content:space-between;margin-bottom:8px;font-size:13px}"
59".mining-stat .label{color:#888}"
60".mining-stat .value{color:#f7931a;font-weight:bold}"
49"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" 61"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}"
50"#status.success{display:block;background:#1a472a;color:#4caf50}" 62"#status.success{display:block;background:#1a472a;color:#4caf50}"
51"#status.error{display:block;background:#471a1a;color:#f44336}" 63"#status.error{display:block;background:#471a1a;color:#f44336}"
52"#status.processing{display:block;background:#1a3a47;color:#2196f3}" 64"#status.processing{display:block;background:#1a3a47;color:#2196f3}"
65".mining-info{color:#666;font-size:11px;margin-top:12px;line-height:1.5}"
53"</style>" 66"</style>"
54"</head><body>" 67"</head><body>"
55"<div class='card'>" 68"<div class='card'>"
56"<h1>TollGate</h1>" 69"<h1>TollGate</h1>"
57"<p class='subtitle'>Pay for internet access with ecash</p>" 70"<p class='subtitle'>Pay for internet access with ecash or mining</p>"
71"__MINING_TABS__"
72"<div id='tab-cashu' class='tab-content __CASHU_ACTIVE__'>"
58"<div class='price'>" 73"<div class='price'>"
59"<div class='price-amount'>__PRICE__</div>" 74"<div class='price-amount'>__PRICE__</div>"
60"<div class='price-unit'>sats per minute</div>" 75"<div class='price-unit'>sats per minute</div>"
@@ -66,6 +81,18 @@ static const char PORTAL_HTML_TEMPLATE[] = \
66"<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>" 81"<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>"
67"<div class='mint-hint'>Tap to copy &bull; Mint tokens at this URL before paying</div>" 82"<div class='mint-hint'>Tap to copy &bull; Mint tokens at this URL before paying</div>"
68"</div>" 83"</div>"
84"</div>"
85"<div id='tab-mining' class='tab-content __MINING_ACTIVE__'>"
86"<div class='mining-stats'>"
87"<div class='mining-stat'><span class='label'>Hashrate</span><span class='value' id='hashrate'>0.00 GH/s</span></div>"
88"<div class='mining-stat'><span class='label'>Shares</span><span class='value' id='shareCount'>0</span></div>"
89"<div class='mining-stat'><span class='label'>Hashprice</span><span class='value' id='hashprice'>0.00 sat/GH/s/day</span></div>"
90"<div class='mining-stat'><span class='label'>Time earned</span><span class='value' id='timeEarned'>0 min</span></div>"
91"</div>"
92"<button class='btn' id='mineBtn' onclick='toggleMining()'>Start Mining</button>"
93"<div class='mining-info'>Mining earns internet time by contributing SHA256 hashpower. "
94"Connect a Stratum miner to port __MINING_PORT__ or use the built-in web miner.</div>"
95"</div>"
69"<div id='status'></div>" 96"<div id='status'></div>"
70"</div>" 97"</div>"
71"<script>" 98"<script>"
@@ -74,6 +101,14 @@ static const char PORTAL_HTML_TEMPLATE[] = \
74"const statusEl=document.getElementById('status');" 101"const statusEl=document.getElementById('status');"
75"const payBtn=document.getElementById('payBtn');" 102"const payBtn=document.getElementById('payBtn');"
76"const tokenInput=document.getElementById('tokenInput');" 103"const tokenInput=document.getElementById('tokenInput');"
104"let miningActive=false;"
105"let miningInterval=null;"
106"function switchTab(tab){"
107"document.querySelectorAll('.tab').forEach(t=>t.classList.remove('active'));"
108"document.querySelectorAll('.tab-content').forEach(t=>t.classList.remove('active'));"
109"event.target.classList.add('active');"
110"document.getElementById('tab-'+tab).classList.add('active');"
111"}"
77"function copyMint(){" 112"function copyMint(){"
78"if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);" 113"if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);"
79"mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}" 114"mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}"
@@ -93,6 +128,24 @@ static const char PORTAL_HTML_TEMPLATE[] = \
93"else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" 128"else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}"
94"}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});" 129"}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});"
95"}" 130"}"
131"function pollMiningStats(){"
132"fetch('http://__AP_IP__:2121/mining/stats').then(r=>r.json()).then(d=>{"
133"document.getElementById('hashrate').textContent=d.proxy.hashrate_ghs.toFixed(2)+' GH/s';"
134"document.getElementById('shareCount').textContent=d.proxy.total_accepted;"
135"document.getElementById('hashprice').textContent=d.proxy.hashprice.toFixed(2)+' sat/GH/s/day';"
136"}).catch(()=>{});"
137"fetch('http://__AP_IP__:2121/usage').then(r=>r.text()).then(t=>{"
138"if(t&&t!=='-1/-1'){"
139"const parts=t.split('/');const rem=Math.floor(parseInt(parts[0])/60000);"
140"document.getElementById('timeEarned').textContent=rem+' min';"
141"}}).catch(()=>{});"
142"}"
143"function toggleMining(){"
144"if(miningActive){miningActive=false;clearInterval(miningInterval);"
145"document.getElementById('mineBtn').textContent='Start Mining';return;}"
146"miningActive=true;document.getElementById('mineBtn').textContent='Mining...';"
147"miningInterval=setInterval(pollMiningStats,2000);pollMiningStats();"
148"}"
96"</script>" 149"</script>"
97"</body></html>"; 150"</body></html>";
98 151
@@ -126,7 +179,21 @@ static esp_err_t portal_handler(httpd_req_t *req)
126 { "__AP_IP__", s_ap_ip_str }, 179 { "__AP_IP__", s_ap_ip_str },
127 { "__PRICE__", price_str }, 180 { "__PRICE__", price_str },
128 { "__MINT_URL__", cfg->mint_url }, 181 { "__MINT_URL__", cfg->mint_url },
182 { "__MINING_TABS__", cfg->mining_enabled ?
183 "<div class='tabs'>"
184 "<button class='tab active' onclick=\"switchTab('cashu')\">Cashu</button>"
185 "<button class='tab' onclick=\"switchTab('mining')\">Mine</button>"
186 "</div>" : "" },
187 { "__MINING_PORT__", cfg->mining_enabled ?
188 (char[]){ [0 ... 7] = 0 } : "3333" },
189 { "__CASHU_ACTIVE__", "active" },
190 { "__MINING_ACTIVE__", "" },
129 }; 191 };
192 char mining_port_buf[8] = "3333";
193 if (cfg->mining_enabled) {
194 snprintf(mining_port_buf, sizeof(mining_port_buf), "%d", cfg->mining_port);
195 subs[4].val = mining_port_buf;
196 }
130 int nsubs = sizeof(subs) / sizeof(subs[0]); 197 int nsubs = sizeof(subs) / sizeof(subs[0]);
131 198
132 size_t extra = 0; 199 size_t extra = 0;