diff options
Diffstat (limited to 'main/captive_portal.c')
| -rw-r--r-- | main/captive_portal.c | 69 |
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 • Mint tokens at this URL before paying</div>" | 82 | "<div class='mint-hint'>Tap to copy • 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; |