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.c68
1 files changed, 57 insertions, 11 deletions
diff --git a/main/captive_portal.c b/main/captive_portal.c
index 1a3d5ce..c9bcf19 100644
--- a/main/captive_portal.c
+++ b/main/captive_portal.c
@@ -2,6 +2,7 @@
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 "mint_health.h"
5#include "esp_log.h" 6#include "esp_log.h"
6#include "esp_wifi.h" 7#include "esp_wifi.h"
7#include "cJSON.h" 8#include "cJSON.h"
@@ -42,9 +43,14 @@ static const char PORTAL_HTML_TEMPLATE[] = \
42".btn:disabled{background:#333;color:#666;cursor:not-allowed}" 43".btn:disabled{background:#333;color:#666;cursor:not-allowed}"
43".mints{background:#252525;border-radius:12px;padding:12px;margin-top:16px;text-align:left}" 44".mints{background:#252525;border-radius:12px;padding:12px;margin-top:16px;text-align:left}"
44".mints-title{color:#888;font-size:12px;margin-bottom:8px}" 45".mints-title{color:#888;font-size:12px;margin-bottom:8px}"
45".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all;" 46".mint-item{display:flex;align-items:center;padding:6px 8px;margin-bottom:4px;"
46"background:#1a1a1a;padding:8px;border-radius:6px;cursor:pointer}" 47"background:#1a1a1a;border-radius:6px;cursor:pointer}"
47".mint-url:active{opacity:0.7}" 48".mint-item:active{opacity:0.7}"
49".mint-dot{width:8px;height:8px;border-radius:50%;margin-right:8px;flex-shrink:0}"
50".mint-dot.green{background:#4caf50}"
51".mint-dot.grey{background:#666}"
52".mint-url{font-family:monospace;font-size:11px;color:#f7931a;word-break:break-all}"
53".mint-url.dim{color:#666}"
48".mint-hint{color:#666;font-size:10px;margin-top:4px}" 54".mint-hint{color:#666;font-size:10px;margin-top:4px}"
49"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}" 55"#status{margin-top:16px;padding:12px;border-radius:8px;display:none;font-size:14px}"
50"#status.success{display:block;background:#1a472a;color:#4caf50}" 56"#status.success{display:block;background:#1a472a;color:#4caf50}"
@@ -63,20 +69,21 @@ static const char PORTAL_HTML_TEMPLATE[] = \
63"<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>" 69"<button class='btn' id='payBtn' onclick='payToken()'>Pay & Connect</button>"
64"<div class='mints'>" 70"<div class='mints'>"
65"<div class='mints-title'>SUPPORTED MINTS</div>" 71"<div class='mints-title'>SUPPORTED MINTS</div>"
66"<div class='mint-url' id='mintUrl' onclick='copyMint()'>__MINT_URL__</div>" 72"<div id='mintList'>__MINT_LIST__</div>"
67"<div class='mint-hint'>Tap to copy &bull; Mint tokens at this URL before paying</div>" 73"<div class='mint-hint'>Tap to copy &bull; Green = reachable</div>"
68"</div>" 74"</div>"
69"<div id='status'></div>" 75"<div id='status'></div>"
70"</div>" 76"</div>"
71"<script>" 77"<script>"
72"const mintUrlEl=document.getElementById('mintUrl');" 78"const mintListEl=document.getElementById('mintList');"
73"const mintUrl=mintUrlEl.textContent;"
74"const statusEl=document.getElementById('status');" 79"const statusEl=document.getElementById('status');"
75"const payBtn=document.getElementById('payBtn');" 80"const payBtn=document.getElementById('payBtn');"
76"const tokenInput=document.getElementById('tokenInput');" 81"const tokenInput=document.getElementById('tokenInput');"
77"function copyMint(){" 82"function copyMint(url){"
78"if(navigator.clipboard){navigator.clipboard.writeText(mintUrl);" 83"if(navigator.clipboard){navigator.clipboard.writeText(url);"
79"mintUrlEl.textContent='Copied!';setTimeout(()=>{mintUrlEl.textContent=mintUrl;},1000);}" 84"const el=event.currentTarget;const u=el.querySelector('.mint-url');"
85"const orig=u.textContent;u.textContent='Copied!';"
86"setTimeout(()=>{u.textContent=orig;},1000);}"
80"}" 87"}"
81"function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}" 88"function showStatus(msg,type){statusEl.textContent=msg;statusEl.className=type;}"
82"function payToken(){" 89"function payToken(){"
@@ -93,6 +100,20 @@ static const char PORTAL_HTML_TEMPLATE[] = \
93"else if(d.kind===21023){showStatus('Error: '+(d.content||'Unknown error'),'error');payBtn.disabled=false;}" 100"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;});" 101"}).catch(e=>{showStatus(e.message||'Connection error','error');payBtn.disabled=false;});"
95"}" 102"}"
103"function refreshMints(){"
104"fetch('http://__AP_IP__:2121/mints').then(r=>r.json()).then(data=>{"
105"let html='';"
106"for(const m of data){"
107"const cls=m.reachable?'green':'grey';"
108"const urlCls=m.reachable?'mint-url':'mint-url dim';"
109"html+='<div class=\"mint-item\" onclick=\"copyMint(\\''+m.url+'\\')\">';"
110"html+='<span class=\"mint-dot '+cls+'\"></span>';"
111"html+='<span class=\"'+urlCls+'\">'+m.url+'</span></div>';"
112"}"
113"if(html)mintListEl.innerHTML=html;"
114"}).catch(()=>{});"
115"}"
116"setInterval(refreshMints,30000);"
96"</script>" 117"</script>"
97"</body></html>"; 118"</body></html>";
98 119
@@ -122,10 +143,35 @@ static esp_err_t portal_handler(httpd_req_t *req)
122 const char *tpl = PORTAL_HTML_TEMPLATE; 143 const char *tpl = PORTAL_HTML_TEMPLATE;
123 size_t tpl_len = strlen(tpl); 144 size_t tpl_len = strlen(tpl);
124 145
146 char mint_list_html[4096];
147 size_t mint_list_cap = sizeof(mint_list_html);
148 size_t mint_list_len = 0;
149 mint_list_html[0] = '\0';
150 int mint_count = 0;
151 const mint_status_t *mints = mint_health_get_all(&mint_count);
152 for (int i = 0; i < mint_count; i++) {
153 const char *cls = mints[i].reachable ? "green" : "grey";
154 const char *url_cls = mints[i].reachable ? "mint-url" : "mint-url dim";
155 int written = snprintf(mint_list_html + mint_list_len, mint_list_cap - mint_list_len,
156 "<div class='mint-item' onclick='copyMint(\"%s\")'>"
157 "<span class='mint-dot %s'></span>"
158 "<span class='%s'>%s</span></div>",
159 mints[i].url, cls, url_cls, mints[i].url);
160 if (written > 0 && (size_t)written < mint_list_cap - mint_list_len) {
161 mint_list_len += (size_t)written;
162 }
163 }
164 if (mint_count == 0) {
165 const tollgate_config_t *cfg = tollgate_config_get();
166 snprintf(mint_list_html, sizeof(mint_list_html),
167 "<div class='mint-item'><span class='mint-dot grey'></span>"
168 "<span class='mint-url dim'>%s</span></div>", cfg->mint_url);
169 }
170
125 struct { const char *key; const char *val; } subs[] = { 171 struct { const char *key; const char *val; } subs[] = {
126 { "__AP_IP__", s_ap_ip_str }, 172 { "__AP_IP__", s_ap_ip_str },
127 { "__PRICE__", price_str }, 173 { "__PRICE__", price_str },
128 { "__MINT_URL__", cfg->mint_url }, 174 { "__MINT_LIST__", mint_list_html },
129 }; 175 };
130 int nsubs = sizeof(subs) / sizeof(subs[0]); 176 int nsubs = sizeof(subs) / sizeof(subs[0]);
131 177