mirror of https://github.com/lukechilds/lnbits.git
8 changed files with 527 additions and 555 deletions
@ -0,0 +1,16 @@ |
|||||
|
import json |
||||
|
import sqlite3 |
||||
|
|
||||
|
|
||||
|
class MegaEncoder(json.JSONEncoder): |
||||
|
def default(self, o): |
||||
|
if type(o) == sqlite3.Row: |
||||
|
val = {} |
||||
|
for k in o.keys(): |
||||
|
val[k] = o[k] |
||||
|
return val |
||||
|
return o |
||||
|
|
||||
|
|
||||
|
def megajson(o): |
||||
|
return json.dumps(o, cls=MegaEncoder) |
@ -0,0 +1,349 @@ |
|||||
|
/** @format */ |
||||
|
|
||||
|
const user = window.user |
||||
|
const user_wallets = window.user_wallets |
||||
|
const wallet = window.wallet |
||||
|
const transactions = window.transactions |
||||
|
|
||||
|
var thehash = '' |
||||
|
var theinvoice = '' |
||||
|
var outamount = '' |
||||
|
var outmemo = '' |
||||
|
|
||||
|
// API CALLS
|
||||
|
|
||||
|
function postAjax(url, data, thekey, success) { |
||||
|
var params = |
||||
|
typeof data == 'string' |
||||
|
? data |
||||
|
: Object.keys(data) |
||||
|
.map(function(k) { |
||||
|
return encodeURIComponent(k) + '=' + encodeURIComponent(data[k]) |
||||
|
}) |
||||
|
.join('&') |
||||
|
var xhr = window.XMLHttpRequest |
||||
|
? new XMLHttpRequest() |
||||
|
: new ActiveXObject('Microsoft.XMLHTTP') |
||||
|
xhr.open('POST', url) |
||||
|
xhr.onreadystatechange = function() { |
||||
|
if (xhr.readyState > 3 && xhr.status == 200) { |
||||
|
success(xhr.responseText) |
||||
|
} |
||||
|
} |
||||
|
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey) |
||||
|
xhr.setRequestHeader('Content-Type', 'application/json') |
||||
|
xhr.send(params) |
||||
|
return xhr |
||||
|
} |
||||
|
|
||||
|
function getAjax(url, thekey, success) { |
||||
|
var xhr = window.XMLHttpRequest |
||||
|
? new XMLHttpRequest() |
||||
|
: new ActiveXObject('Microsoft.XMLHTTP') |
||||
|
xhr.open('GET', url, true) |
||||
|
xhr.onreadystatechange = function() { |
||||
|
if (xhr.readyState > 3 && xhr.status == 200) { |
||||
|
success(xhr.responseText) |
||||
|
} |
||||
|
} |
||||
|
xhr.setRequestHeader('Grpc-Metadata-macaroon', thekey) |
||||
|
xhr.setRequestHeader('Content-Type', 'application/json') |
||||
|
xhr.send() |
||||
|
return xhr |
||||
|
} |
||||
|
|
||||
|
function sendfundsinput() { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<br/><br/><div class='row'><div class='col-md-4'>" + |
||||
|
"<textarea id='pasteinvoice' class='form-control' rows='3' placeholder='Paste an invoice'></textarea></div></div>" + |
||||
|
"<br/><div class='row'><div class='col-md-4'><button type='submit' onclick='sendfundspaste()' class='btn btn-primary'>" + |
||||
|
"Submit</button><button style='margin-left:20px;' type='submit' class='btn btn-primary' onclick='scanQRsend()'>" + |
||||
|
'Use camera to scan an invoice</button></div></div><br/><br/>' |
||||
|
document.getElementById('receive').innerHTML = '' |
||||
|
} |
||||
|
|
||||
|
function sendfundspaste() { |
||||
|
invoice = document.getElementById('pasteinvoice').value |
||||
|
theinvoice = decode(invoice) |
||||
|
outmemo = theinvoice.data.tags[1].value |
||||
|
outamount = Number(theinvoice.human_readable_part.amount) / 1000 |
||||
|
if (outamount > Number(wallet.balance)) { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<div class='row'><div class='col-md-6'>" + |
||||
|
"<h3><b style='color:red;'>Not enough funds!</b></h3>" + |
||||
|
"<button style='margin-left:20px;' type='submit' class='btn btn-primary' onclick='cancelsend()'>Continue</button>" + |
||||
|
'</br/></br/></div></div>' |
||||
|
} else { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<div class='row'><div class='col-md-6'>" + |
||||
|
'<h3><b>Invoice details</b></br/>Amount: ' + |
||||
|
outamount + |
||||
|
'<br/>Memo: ' + |
||||
|
outmemo + |
||||
|
'</h3>' + |
||||
|
"<h4 style='word-wrap: break-word;'>" + |
||||
|
invoice + |
||||
|
'</h4>' + |
||||
|
"<button type='submit' class='btn btn-primary' onclick='sendfunds(" + |
||||
|
JSON.stringify(invoice) + |
||||
|
")'>Send funds</button>" + |
||||
|
"<button style='margin-left:20px;' type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel payment</button>" + |
||||
|
'</br/></br/></div></div>' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function receive() { |
||||
|
document.getElementById('receive').innerHTML = |
||||
|
"<br/><div class='row'><div id='QRCODE'>" + |
||||
|
"<div class='col-sm-2'><input type='number' class='form-control' id='amount' placeholder='Amount' max='1000000' required></div>" + |
||||
|
"<div class='col-sm-2'><input type='text' class='form-control' id='memo' placeholder='Memo' required></div>" + |
||||
|
"<div class='col-sm-2'><input type='button' id='getinvoice' onclick='received()' class='btn btn-primary' value='Create invoice' /></div>" + |
||||
|
'</div></div><br/>' |
||||
|
document.getElementById('sendfunds').innerHTML = '' |
||||
|
} |
||||
|
|
||||
|
function received() { |
||||
|
memo = document.getElementById('memo').value |
||||
|
amount = document.getElementById('amount').value |
||||
|
postAjax( |
||||
|
'/v1/invoices', |
||||
|
JSON.stringify({value: amount, memo: memo}), |
||||
|
wallet.inkey, |
||||
|
function(data) { |
||||
|
theinvoice = JSON.parse(data).pay_req |
||||
|
thehash = JSON.parse(data).payment_hash |
||||
|
document.getElementById('QRCODE').innerHTML = |
||||
|
"<div class='col-md-4'><div class='box'><div class='box-header'>" + |
||||
|
"<center><a href='lightning:" + |
||||
|
theinvoice + |
||||
|
"'><div id='qrcode'></div></a>" + |
||||
|
"<p style='word-wrap: break-word;'>" + |
||||
|
theinvoice + |
||||
|
'</p></div></div></div></center>' |
||||
|
|
||||
|
new QRCode(document.getElementById('qrcode'), { |
||||
|
text: theinvoice, |
||||
|
width: 300, |
||||
|
height: 300, |
||||
|
colorDark: '#000000', |
||||
|
colorLight: '#ffffff', |
||||
|
correctLevel: QRCode.CorrectLevel.M |
||||
|
}) |
||||
|
getAjax('/v1/invoice/' + thehash, wallet.inkey, function(datab) { |
||||
|
console.log(JSON.parse(datab).PAID) |
||||
|
if (JSON.parse(datab).PAID == 'TRUE') { |
||||
|
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
function cancelsend() { |
||||
|
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user |
||||
|
} |
||||
|
|
||||
|
function sendfunds(invoice) { |
||||
|
var url = '/v1/channels/transactions' |
||||
|
postAjax( |
||||
|
url, |
||||
|
JSON.stringify({payment_request: invoice}), |
||||
|
wallet.adminkey, |
||||
|
function(data) { |
||||
|
thehash = JSON.parse(data).payment_hash |
||||
|
console.log(JSON.parse(data)) |
||||
|
if (JSON.parse(data).PAID == 'TRUE') { |
||||
|
window.location.href = 'wallet?wal=' + wallet.id + '&usr=' + user |
||||
|
} |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
function scanQRsend() { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<br/><br/><div class='row'><div class='col-md-4'>" + |
||||
|
"<div id='loadingMessage'>🎥 Unable to access video stream (please make sure you have a webcam enabled)</div>" + |
||||
|
"<canvas id='canvas' hidden></canvas><div id='output' hidden><div id='outputMessage'></div>" + |
||||
|
"<br/><span id='outputData'></span></div></div></div><button type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel</button><br/><br/>" |
||||
|
var video = document.createElement('video') |
||||
|
var canvasElement = document.getElementById('canvas') |
||||
|
var canvas = canvasElement.getContext('2d') |
||||
|
var loadingMessage = document.getElementById('loadingMessage') |
||||
|
var outputContainer = document.getElementById('output') |
||||
|
var outputMessage = document.getElementById('outputMessage') |
||||
|
var outputData = document.getElementById('outputData') |
||||
|
function drawLine(begin, end, color) { |
||||
|
canvas.beginPath() |
||||
|
canvas.moveTo(begin.x, begin.y) |
||||
|
canvas.lineTo(end.x, end.y) |
||||
|
canvas.lineWidth = 4 |
||||
|
canvas.strokeStyle = color |
||||
|
canvas.stroke() |
||||
|
} |
||||
|
// Use facingMode: environment to attemt to get the front camera on phones
|
||||
|
navigator.mediaDevices |
||||
|
.getUserMedia({video: {facingMode: 'environment'}}) |
||||
|
.then(function(stream) { |
||||
|
video.srcObject = stream |
||||
|
video.setAttribute('playsinline', true) // required to tell iOS safari we don't want fullscreen
|
||||
|
video.play() |
||||
|
requestAnimationFrame(tick) |
||||
|
}) |
||||
|
function tick() { |
||||
|
loadingMessage.innerText = '⌛ Loading video...' |
||||
|
if (video.readyState === video.HAVE_ENOUGH_DATA) { |
||||
|
loadingMessage.hidden = true |
||||
|
canvasElement.hidden = false |
||||
|
outputContainer.hidden = false |
||||
|
canvasElement.height = video.videoHeight |
||||
|
canvasElement.width = video.videoWidth |
||||
|
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height) |
||||
|
var imageData = canvas.getImageData( |
||||
|
0, |
||||
|
0, |
||||
|
canvasElement.width, |
||||
|
canvasElement.height |
||||
|
) |
||||
|
var code = jsQR(imageData.data, imageData.width, imageData.height, { |
||||
|
inversionAttempts: 'dontInvert' |
||||
|
}) |
||||
|
if (code) { |
||||
|
drawLine( |
||||
|
code.location.topLeftCorner, |
||||
|
code.location.topRightCorner, |
||||
|
'#FF3B58' |
||||
|
) |
||||
|
drawLine( |
||||
|
code.location.topRightCorner, |
||||
|
code.location.bottomRightCorner, |
||||
|
'#FF3B58' |
||||
|
) |
||||
|
drawLine( |
||||
|
code.location.bottomRightCorner, |
||||
|
code.location.bottomLeftCorner, |
||||
|
'#FF3B58' |
||||
|
) |
||||
|
drawLine( |
||||
|
code.location.bottomLeftCorner, |
||||
|
code.location.topLeftCorner, |
||||
|
'#FF3B58' |
||||
|
) |
||||
|
outputMessage.hidden = true |
||||
|
outputData.parentElement.hidden = false |
||||
|
outputData.innerText = JSON.stringify(code.data) |
||||
|
theinvoice = decode(code.data) |
||||
|
outmemo = theinvoice.data.tags[1].value |
||||
|
outamount = Number(theinvoice.human_readable_part.amount) / 1000 |
||||
|
if (outamount > Number(wallet.balance)) { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<div class='row'><div class='col-md-6'>" + |
||||
|
"<h3><b style='color:red;'>Not enough funds!</b></h3>" + |
||||
|
"<button style='margin-left:20px;' type='submit' class='btn btn-primary' onclick='cancelsend()'>Continue</button>" + |
||||
|
'</br/></br/></div></div>' |
||||
|
} else { |
||||
|
document.getElementById('sendfunds').innerHTML = |
||||
|
"<div class='row'><div class='col-md-6'>" + |
||||
|
'<h3><b>Invoice details</b></br/>Amount: ' + |
||||
|
outamount + |
||||
|
'<br/>Memo: ' + |
||||
|
outmemo + |
||||
|
'</h3>' + |
||||
|
"<h4 style='word-wrap: break-word;'>" + |
||||
|
JSON.stringify(code.data) + |
||||
|
'</h4>' + |
||||
|
"<button type='submit' class='btn btn-primary' onclick='sendfunds(" + |
||||
|
JSON.stringify(code.data) + |
||||
|
")'>Send funds</button>" + |
||||
|
"<button style='margin-left:20px;' type='submit' class='btn btn-primary' onclick='cancelsend()'>Cancel payment</button>" + |
||||
|
'</br/></br/></div></div>' |
||||
|
} |
||||
|
} else { |
||||
|
outputMessage.hidden = false |
||||
|
outputData.parentElement.hidden = true |
||||
|
} |
||||
|
} |
||||
|
requestAnimationFrame(tick) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function deletewallet() { |
||||
|
var url = 'deletewallet?wal=' + wallet.id + '&usr=' + user |
||||
|
window.location.href = url |
||||
|
} |
||||
|
|
||||
|
function sidebarmake() { |
||||
|
document.getElementById('sidebarmake').innerHTML = |
||||
|
"<li><div class='form-group'>" + |
||||
|
"<input style='width:70%;float:left;' type='text' class='form-control' id='walname' placeholder='Name wallet' required>" + |
||||
|
"<button style='width:30%;float:left;' type='button' class='btn btn-primary' onclick='newwallet()'>Submit</button>" + |
||||
|
'</div></li><br/><br/>' |
||||
|
} |
||||
|
|
||||
|
function newwallet() { |
||||
|
walname = document.getElementById('walname').value |
||||
|
window.location.href = 'wallet?usr=' + user + '&nme=' + walname |
||||
|
} |
||||
|
|
||||
|
function drawChart(transactions) { |
||||
|
var linechart = [] |
||||
|
var transactionsHTML = '' |
||||
|
var balance = 0 |
||||
|
|
||||
|
for (var i = 0; i < transactions.length; i++) { |
||||
|
var tx = transactions[i] |
||||
|
var datime = convertTimestamp(tx.time) |
||||
|
|
||||
|
// make the transactions table
|
||||
|
transactionsHTML += |
||||
|
"<tr><td style='width: 50%'>" + |
||||
|
tx.memo + |
||||
|
'</td><td>' + |
||||
|
datime + |
||||
|
'</td><td>' + |
||||
|
parseFloat(tx.amount / 1000) + |
||||
|
'</td></tr>' |
||||
|
|
||||
|
// make the line chart
|
||||
|
balance += parseInt(tx.amount / 1000) |
||||
|
linechart.push({y: datime, balance: balance}) |
||||
|
} |
||||
|
|
||||
|
document.getElementById('transactions').innerHTML = transactionsHTML |
||||
|
|
||||
|
if (linechart[0] != '') { |
||||
|
document.getElementById('satschart').innerHTML = |
||||
|
"<div class='row'><div class='col-md-6'><div class='box box-info'><div class='box-header'>" + |
||||
|
"<h3 class='box-title'>Spending</h3></div><div class='box-body chart-responsive'>" + |
||||
|
"<div class='chart' id='line-chart' style='height: 300px;'></div></div></div></div></div>" |
||||
|
} |
||||
|
|
||||
|
console.log(linechart) |
||||
|
var line = new Morris.Line({ |
||||
|
element: 'line-chart', |
||||
|
resize: true, |
||||
|
data: linechart, |
||||
|
xkey: 'y', |
||||
|
ykeys: ['balance'], |
||||
|
labels: ['balance'], |
||||
|
lineColors: ['#3c8dbc'], |
||||
|
hideHover: 'auto' |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
function convertTimestamp(timestamp) { |
||||
|
var d = new Date(timestamp * 1000), |
||||
|
yyyy = d.getFullYear(), |
||||
|
mm = ('0' + (d.getMonth() + 1)).slice(-2), |
||||
|
dd = ('0' + d.getDate()).slice(-2), |
||||
|
hh = d.getHours(), |
||||
|
h = hh, |
||||
|
min = ('0' + d.getMinutes()).slice(-2), |
||||
|
ampm = 'AM', |
||||
|
time |
||||
|
time = yyyy + '-' + mm + '-' + dd + ' ' + h + ':' + min |
||||
|
return time |
||||
|
} |
||||
|
|
||||
|
if (transactions.length) { |
||||
|
drawChart(transactions) |
||||
|
} |
Loading…
Reference in new issue