Skip to content

Commit d4e90c0

Browse files
Replace CDN QR script with locally generated tunnel invoice QR codes
1 parent bd09aad commit d4e90c0

6 files changed

Lines changed: 67 additions & 9 deletions

File tree

nixos/admin-app/app.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"""LNbitsBox Admin Dashboard — system monitoring and service management"""
33

44
import json
5+
import io
56
import os
67
import shlex
78
import sys
@@ -1453,6 +1454,33 @@ def api_tunnel_stop():
14531454
return jsonify({"status": "error", "message": str(e)}), 500
14541455

14551456

1457+
@app.route("/box/api/qrcode", methods=["POST"])
1458+
@login_required
1459+
def api_qrcode():
1460+
payload = request.get_json(silent=True) or {}
1461+
text = str(payload.get("text", "")).strip()
1462+
if not text:
1463+
return _json_error("QR payload is required", 400)
1464+
if len(text) > 4096:
1465+
return _json_error("QR payload is too large", 400)
1466+
1467+
try:
1468+
import qrcode
1469+
1470+
qr = qrcode.QRCode(box_size=8, border=2)
1471+
qr.add_data(text)
1472+
qr.make(fit=True)
1473+
image = qr.make_image(fill_color="black", back_color="white")
1474+
image_bytes = io.BytesIO()
1475+
image.save(image_bytes, format="PNG")
1476+
image_bytes.seek(0)
1477+
response = send_file(image_bytes, mimetype="image/png")
1478+
response.headers["Cache-Control"] = "no-store"
1479+
return response
1480+
except Exception as e:
1481+
return _json_error(str(e), 500)
1482+
1483+
14561484
# ── OTA Update Endpoints ─────────────────────────────────────────
14571485

14581486
def get_current_version():

nixos/admin-app/static/js/dashboard-tunnel.js

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,50 @@
252252
}
253253
};
254254

255+
D.renderTunnelInvoiceQr = async function (bolt11) {
256+
const qrEl = D.el('tunnel-invoice-qr');
257+
if (!qrEl) return;
258+
qrEl.replaceChildren();
259+
if (!bolt11) return;
260+
261+
try {
262+
const resp = await fetch('/box/api/qrcode', {
263+
method: 'POST',
264+
headers: { 'Content-Type': 'application/json' },
265+
body: JSON.stringify({ text: bolt11 }),
266+
});
267+
if (!resp.ok) {
268+
throw new Error('QR render failed');
269+
}
270+
const blob = await resp.blob();
271+
const imageUrl = URL.createObjectURL(blob);
272+
const image = document.createElement('img');
273+
image.src = imageUrl;
274+
image.alt = 'Tunnel invoice QR code';
275+
image.width = 220;
276+
image.height = 220;
277+
image.className = 'block';
278+
image.addEventListener('load', function () {
279+
URL.revokeObjectURL(imageUrl);
280+
}, { once: true });
281+
qrEl.appendChild(image);
282+
} catch (error) {
283+
const message = document.createElement('p');
284+
message.className = 'text-red-400 font-mono text-xs text-center p-4';
285+
message.textContent = 'Failed to render QR code.';
286+
qrEl.appendChild(message);
287+
}
288+
};
289+
255290
D.openTunnelInvoiceModal = function (bolt11) {
256291
const modal = D.el('tunnel-invoice-modal');
257292
const textarea = D.el('tunnel-invoice-text');
258293
const status = D.el('tunnel-invoice-status');
259-
const qrEl = D.el('tunnel-invoice-qr');
260-
if (!modal || !textarea || !status || !qrEl) return;
294+
if (!modal || !textarea || !status) return;
261295
modal.classList.remove('hidden');
262296
textarea.value = bolt11 || '';
263297
status.textContent = 'Waiting for payment...';
264-
qrEl.innerHTML = '';
265-
if (bolt11 && typeof QRCode !== 'undefined') {
266-
D.state.tunnelInvoiceQr = new QRCode(qrEl, { text: bolt11, width: 220, height: 220, correctLevel: QRCode.CorrectLevel.M });
267-
}
298+
D.renderTunnelInvoiceQr(bolt11);
268299
};
269300

270301
D.closeTunnelInvoiceModal = function () {

nixos/admin-app/templates/dashboard.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
{% block head %}
44
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
5-
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
65
{% endblock %}
76

87
{% block content %}

nixos/admin-app/templates/overview.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
{% endblock %}
2222

2323
{% block page_scripts %}
24-
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
2524
<script src="{{ url_for('static', filename='js/dashboard-core.js') }}"></script>
2625
<script id="initial-tunnel-status" type="application/json">{{ initial_tunnel_status|tojson }}</script>
2726
<script src="{{ url_for('static', filename='js/dashboard-services.js') }}"></script>

nixos/admin-app/templates/remote_access.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
{% endblock %}
1313

1414
{% block page_scripts %}
15-
<script src="https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js"></script>
1615
<script src="{{ url_for('static', filename='js/dashboard-core.js') }}"></script>
1716
<script id="initial-tunnel-status" type="application/json">{{ initial_tunnel_status|tojson }}</script>
1817
<script src="{{ url_for('static', filename='js/dashboard-services.js') }}"></script>

nixos/admin-package.nix

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ let
55
flask
66
ps."flask-wtf" # CSRF protection
77
mnemonic
8+
pillow
89
psutil
10+
qrcode
911
requests
1012
]);
1113
in

0 commit comments

Comments
 (0)