Browse Source

big bundle of changes.

* reorganize templates even more (and a small layout break)
* rename wallets.hash and accounts.userhash to id
* refactor /wallets so it's idempotent for each param combination
* many small changes
fee_issues
fiatjaf 5 years ago
parent
commit
14dfa9ecc6
  1. 235
      LNbits/__init__.py
  2. 8
      LNbits/data/schema.sql
  3. 1
      LNbits/db.py
  4. 5
      LNbits/helpers.py
  5. 1
      LNbits/settings.py
  6. 55
      LNbits/templates/base.html
  7. 80
      LNbits/templates/index.html
  8. 716
      LNbits/templates/wallet.html

235
LNbits/__init__.py

@ -1,13 +1,13 @@
import lnurl
import uuid
import os
import requests
from flask import Flask, jsonify, render_template, request
from flask import Flask, jsonify, render_template, request, redirect, url_for
from . import bolt11
from .db import Database
from .helpers import encrypt
from .settings import DATABASE_PATH, LNBITS_PATH, WALLET
from .settings import DATABASE_PATH, LNBITS_PATH, WALLET, DEFAULT_USER_WALLET_NAME
app = Flask(__name__)
@ -35,21 +35,24 @@ def home():
@app.route("/deletewallet")
def deletewallet():
theid = request.args.get("usr")
thewal = request.args.get("wal")
with Database() as db:
wallet_row = db.fetchone("SELECT * FROM wallets WHERE hash = ?", (thewal,))
if not wallet_row:
return render_template("index.html")
db.execute("UPDATE wallets SET user = ? WHERE hash = ?", (f"del{wallet_row[4]}", wallet_row[0]))
db.execute("UPDATE wallets SET adminkey = ? WHERE hash = ?", (f"del{wallet_row[5]}", wallet_row[0]))
db.execute("UPDATE wallets SET inkey = ? WHERE hash = ?", (f"del{wallet_row[6]}", wallet_row[0]))
db.execute(
"""
UPDATE wallets AS w SET
user = 'del:' || w.user,
adminkey = 'del:' || w.adminkey,
inkey = 'del:' || w.inkey
WHERE id = ? AND user = ?
""",
(thewal, theid),
)
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (wallet_row[4],))
if user_wallets:
return render_template("deletewallet.html", theid=user_wallets[0][4], thewal=user_wallets[0][0])
next_wallet = db.fetchone("SELECT hash FROM wallets WHERE user = ?", (theid,))
if next_wallet:
return redirect(url_for("wallet", usr=theid, wal=next_wallet[0]))
return render_template("index.html")
@ -60,13 +63,13 @@ def lnurlwallet():
invoice = WALLET.create_invoice(withdraw_res.max_sats).json()
payment_hash = invoice["payment_hash"]
rrr = requests.get(
r = requests.get(
withdraw_res.callback.base,
params={**withdraw_res.callback.query_params, **{"k1": withdraw_res.k1, "pr": invoice["pay_req"]}},
)
dataaa = rrr.json()
data = r.json()
if dataaa["status"] != "OK":
if data["status"] != "OK":
"""TODO: show some kind of error?"""
return render_template("index.html")
@ -76,112 +79,104 @@ def lnurlwallet():
data = r.json()
with Database() as db:
adminkey = encrypt(payment_hash)[0:20]
inkey = encrypt(adminkey)[0:20]
thewal = encrypt(inkey)[0:20]
theid = encrypt(thewal)[0:20]
thenme = "Bitcoin LN Wallet"
db.execute("INSERT INTO accounts (userhash) VALUES (?)", (theid,))
adminkey = encrypt(theid)
inkey = encrypt(adminkey)
adminkey = uuid.uuid4().hex
inkey = uuid.uuid4().hex
thewal = uuid.uuid4().hex
theid = uuid.uuid4().hex
thenme = DEFAULT_USER_WALLET_NAME
db.execute("INSERT INTO accounts (id) VALUES (?)", (theid,))
db.execute(
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES (?, ?, ?, ?, ?)",
"INSERT INTO wallets (id, name, user, adminkey, inkey) VALUES (?, ?, ?, ?, ?)",
(thewal, thenme, theid, adminkey, inkey),
)
return render_template(
"lnurlwallet.html",
len=len("1"),
walnme=thenme,
walbal=withdraw_res.max_sats,
theid=theid,
thewal=thewal,
adminkey=adminkey,
inkey=inkey,
)
return redirect(url_for("wallet", usr=theid, wal=thewal))
@app.route("/wallet")
def wallet():
theid = request.args.get("usr")
thewal = request.args.get("wal")
thenme = request.args.get("nme")
if not thewal:
return render_template("index.html")
usr = request.args.get("usr")
wallet_id = request.args.get("wal")
wallet_name = request.args.get("nme") or DEFAULT_USER_WALLET_NAME
# just usr: return a the first user wallet or create one if none found
# usr and wallet_id: return that wallet or create it if it doesn't exist
# usr, wallet_id and wallet_name: same as above, but use the specified name
# usr and wallet_name: generate a wallet_id and create
# wallet_id and wallet_name: create a user, then move an existing wallet or create
# just wallet_name: create a user, then generate a wallet_id and create
# nothing: create everything
with Database() as db:
user_exists = db.fetchone("SELECT * FROM accounts WHERE userhash = ?", (theid,))
if not user_exists:
# user does not exist: create an account
# --------------------------------------
# ensure this user exists
# -------------------------------
db.execute("INSERT INTO accounts (userhash) VALUES (?)", (theid,))
if not usr:
usr = uuid.uuid4().hex
return redirect(url_for("wallet", usr=usr, wal=wallet_id, nme=wallet_name))
# user exists
# -----------
db.execute(
"""
INSERT INTO accounts (id) VALUES (?)
ON CONFLICT (id) DO NOTHING
""",
(usr,),
)
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (theid,))
user_wallets = db.fetchall("SELECT * FROM wallets WHERE user = ?", (usr,))
if not wallet_id:
# if not given, fetch the first wallet from this user or create
# -------------------------------------------------------------
if user_wallets:
wallet_id = user_wallets[0]["id"]
else:
wallet_id = uuid.uuid4().hex
db.execute(
"""
INSERT INTO wallets (id, name, user, adminkey, inkey)
VALUES (?, ?, ?, ?, ?)
""",
(wallet_id, wallet_name, usr, uuid.uuid4().hex, uuid.uuid4().hex),
)
# user has wallets
# ----------------
return redirect(url_for("wallet", usr=usr, wal=wallet_id, nme=wallet_name))
wallet_row = db.fetchone(
# if wallet_id is given, try to move it to this user or create
# ------------------------------------------------------------
db.execute(
"""
INSERT INTO wallets (id, name, user, adminkey, inkey)
VALUES (?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET user = ?
""",
(wallet_id, wallet_name, usr, uuid.uuid4().hex, uuid.uuid4().hex, usr),
)
# finally, get the wallet with balance and transactions
# -----------------------------------------------------
wallet = db.fetchone(
"""
SELECT
(SELECT balance/1000 FROM balances WHERE wallet = wallets.hash),
coalesce(
(SELECT balance/1000 FROM balances WHERE wallet = wallets.id),
0
) AS balance,
name,
adminkey,
inkey
FROM wallets
WHERE user = ? AND hash = ?
WHERE user = ? AND id = ?
""",
(theid, thewal,),
(usr, wallet_id),
)
transactions = []
return render_template(
"wallet.html",
thearr=user_wallets,
len=len(user_wallets),
walnme=wallet_row[1],
user=theid,
walbal=wallet_row[0],
theid=theid,
thewal=thewal,
transactions=transactions,
adminkey=wallet_row[2],
inkey=wallet_row[3],
)
# user has no wallets
# -------------------
adminkey = encrypt(theid)
inkey = encrypt(adminkey)
db.execute(
"INSERT INTO wallets (hash, name, user, adminkey, inkey) VALUES (?, ?, ?, ?, ?)",
(thewal, thenme, theid, adminkey, inkey),
)
return render_template(
"wallet.html",
len=1,
walnme=thenme,
walbal=0,
theid=theid,
thewal=thewal,
adminkey=adminkey,
inkey=inkey,
transactions=[],
"wallet.html", user_wallets=user_wallets, wallet=wallet, user=usr, transactions=transactions,
)
@ -205,12 +200,12 @@ def api_invoices():
return jsonify({"ERROR": "NO MEMO"}), 400
with Database() as db:
wallet_row = db.fetchone(
"SELECT hash FROM wallets WHERE inkey = ? OR adminkey = ?",
wallet = db.fetchone(
"SELECT id FROM wallets WHERE inkey = ? OR adminkey = ?",
(request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"],),
)
if not wallet_row:
if not wallet:
return jsonify({"ERROR": "NO KEY"}), 200
r = WALLET.create_invoice(postedjson["value"], postedjson["memo"])
@ -218,10 +213,11 @@ def api_invoices():
pay_req = data["pay_req"]
payment_hash = data["payment_hash"]
amount_msat = int(postedjson["value"]) * 1000
db.execute(
"INSERT INTO apipayments (payhash, amount, wallet, pending, memo) VALUES (?, ?, ?, true, ?)",
(payment_hash, int(postedjson["value"]) * 1000, wallet_row[0], postedjson["memo"],),
(payment_hash, amount_msat, wallet["id"], postedjson["memo"],),
)
return jsonify({"pay_req": pay_req, "payment_hash": payment_hash}), 200
@ -238,39 +234,11 @@ def api_transactions():
return jsonify({"ERROR": "NO PAY REQ"}), 200
with Database() as db:
wallet_row = db.fetchone(
"SELECT hash FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],)
)
wallet = db.fetchone("SELECT id FROM wallets WHERE adminkey = ?", (request.headers["Grpc-Metadata-macaroon"],))
if not wallet_row:
if not wallet:
return jsonify({"ERROR": "BAD AUTH"}), 200
# TODO: check this unused code
# move sats calculation to a helper
# ---------------------------------
"""
s = postedjson["payment_request"]
result = re.search("lnbc(.*)1p", s)
tempp = result.group(1)
alpha = ""
num = ""
for i in range(len(tempp)):
if tempp[i].isdigit():
num = num + tempp[i]
else:
alpha += tempp[i]
sats = ""
if alpha == "n":
sats = int(num) / 10
elif alpha == "u":
sats = int(num) * 100
elif alpha == "m":
sats = int(num) * 100000
"""
# ---------------------------------
# decode the invoice
invoice = bolt11.decode(data["payment_request"])
if invoice.amount_msat == 0:
@ -283,13 +251,13 @@ def api_transactions():
invoice.payment_hash,
-int(invoice.amount_msat),
-int(invoice.amount_msat * 0.01),
wallet_row[0],
wallet["id"],
invoice.description,
),
)
# check balance
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet_row[0],))[0]
balance = db.fetchone("SELECT balance/1000 FROM balances WHERE wallet = ?", (wallet["id"],))[0]
if balance < 0:
return jsonify({"ERROR": "INSUFFICIENT BALANCE"}), 403
@ -321,24 +289,23 @@ def api_checkinvoice(payhash):
return jsonify({"ERROR": "MUST BE JSON"}), 200
with Database() as db:
payment_row = db.fetchone(
payment = db.fetchone(
"""
SELECT pending FROM apipayments
INNER JOIN wallets AS w ON apipayments.wallet = w.hash
INNER JOIN wallets AS w ON apipayments.wallet = w.id
WHERE payhash = ?
AND (w.adminkey = ? OR w.inkey = ?)
""",
(payhash, request.headers["Grpc-Metadata-macaroon"], request.headers["Grpc-Metadata-macaroon"]),
)
if not payment_row:
if not payment:
return jsonify({"ERROR": "NO INVOICE"}), 404
if not payment_row[0]: # pending
if not payment["pending"]: # pending
return jsonify({"PAID": "TRUE"}), 200
r = WALLET.get_invoice_status(payhash)
if not r.ok:
return jsonify({"PAID": "FALSE"}), 400

8
LNbits/data/schema.sql

@ -1,11 +1,11 @@
CREATE TABLE IF NOT EXISTS accounts (
userhash text PRIMARY KEY,
id text PRIMARY KEY,
email text,
pass text
);
CREATE TABLE IF NOT EXISTS wallets (
hash text PRIMARY KEY,
id text PRIMARY KEY,
name text NOT NULL,
user text NOT NULL,
adminkey text NOT NULL,
@ -21,9 +21,7 @@ CREATE TABLE IF NOT EXISTS apipayments (
memo text
);
DROP VIEW IF EXISTS balances;
CREATE VIEW balances AS
CREATE VIEW IF NOT EXISTS balances AS
SELECT wallet, coalesce(sum(s), 0) AS balance FROM (
SELECT wallet, sum(amount) AS s -- incoming
FROM apipayments

1
LNbits/db.py

@ -7,6 +7,7 @@ class Database:
def __init__(self, db_path: str = DATABASE_PATH):
self.path = db_path
self.connection = sqlite3.connect(db_path)
self.connection.row_factory = sqlite3.Row
self.cursor = self.connection.cursor()
def __enter__(self):

5
LNbits/helpers.py

@ -1,5 +0,0 @@
import hashlib
def encrypt(string: str):
return hashlib.sha256(string.encode()).hexdigest()

1
LNbits/settings.py

@ -14,3 +14,4 @@ WALLET = LntxbotWallet(
LNBITS_PATH = os.path.dirname(os.path.realpath(__file__))
DATABASE_PATH = os.getenv("DATABASE_PATH") or os.path.join(LNBITS_PATH, "data", "database.sqlite3")
DEFAULT_USER_WALLET_NAME = os.getenv("DEFAULT_USER_WALLET_NAME") or "Bitcoin LN Wallet"

55
LNbits/templates/base.html

@ -239,6 +239,61 @@
></script>
</head>
<body class="skin-blue">
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="/" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a
href="#"
class="sidebar-toggle"
data-toggle="offcanvas"
role="button"
>
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
{% block messages %}{% endblock %}
</li>
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel -->
<!-- /.search form -->
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="header">MENU</li>
{% block menuitems %}{% endblock %}
</ul>
</section>
<!-- /.sidebar -->
</aside>
{% block body %}{% endblock %}
</div>
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>BETA</b>
</div>
<strong
>Learn more about LNbits
<a href="https://github.com/arcbtc/FOSSAW"
>https://github.com/arcbtc/lnbits</a
></strong
>
</footer>
</body>
</html>

80
LNbits/templates/index.html

@ -1,52 +1,17 @@
<!-- @format -->
{% extends "base.html" %} {% block body %}
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="index.html" class="logo"><b>LN</b>bits</a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
<span class="sr-only">Toggle navigation</span>
</a>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu"></li>
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel -->
<!-- /.search form -->
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="header">MAIN NAVIGATION</li>
{% extends "base.html" %} {% block menuitems %}
<li>
<a href="../documentation/index.html"
><i class="fa fa-book"></i> Home</a
>
<a href="/"><i class="fa fa-book"></i> Home</a>
</li>
</ul>
</section>
<!-- /.sidebar -->
</aside>
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<ol class="breadcrumb">
<li>
<a href="/index.html"><i class="fa fa-dashboard"></i> Home</a>
<a href="/"><i class="fa fa-dashboard"></i> Home</a>
</li>
</ol>
<br /><br />
@ -70,20 +35,20 @@
<small>free and open-source lightning wallet</small>
</h1>
<p>
LNbits is a simple, free and open-source lightning-network
wallet for bits and bobs. You can run it on your own server, or
use this one.
LNbits is a simple, free and open-source lightning-network wallet
for bits and bobs. You can run it on your own server, or use this
one.
<br /><br />
The wallet can be used in a variety of ways, an instant wallet
for LN demonstrations, a fallback wallet for the LNURL scheme,
an accounts system to mitigate the risk of exposing applications
to your full balance.
The wallet can be used in a variety of ways, an instant wallet for
LN demonstrations, a fallback wallet for the LNURL scheme, an
accounts system to mitigate the risk of exposing applications to
your full balance.
<br /><br />
The wallet can run on top of LND, lntxbot, paywall, opennode
<br /><br />
Please note that although one of the aims of this wallet is to
mitigate exposure of all your funds, it’s still very BETA and
may in fact do the opposite!
mitigate exposure of all your funds, it’s still very BETA and may
in fact do the opposite!
<br />
<a href="https://github.com/arcbtc/FOSSAW"
>https://github.com/arcbtc/lnbits</a
@ -111,11 +76,7 @@
required
/>
</div>
<button
type="button"
class="btn btn-primary"
onclick="newwallet()"
>
<button type="button" class="btn btn-primary" onclick="newwallet()">
Submit
</button>
</div>
@ -129,19 +90,6 @@
</div>
<!-- /.content-wrapper -->
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>BETA</b>
</div>
<strong
>Learn more about LNbits
<a href="https://github.com/arcbtc/FOSSAW"
>https://github.com/arcbtc/lnbits</a
></strong
>
</footer>
</div>
<script>
function makeid(length) {
var result = ''

716
LNbits/templates/wallet.html

@ -1,110 +1,60 @@
{% extends "base.html" %}
{% block body %}
<div class="wrapper">
<header class="main-header">
<!-- Logo -->
<a href="/" class="logo"><b>LN</b>bits</a></a>
<!-- Header Navbar: style can be found in header.less -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="offcanvas" role="button">
<span class="sr-only">Toggle navigation</span>
</a>
<!-- @format -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<!-- Notifications: style can be found in dropdown.less -->
<li class="dropdown notifications-menu">
{% extends "base.html" %} {% block messages %}
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fa fa-bell-o"></i>
<span class="label label-danger">!</span>
</a>
<ul class="dropdown-menu">
<li class="header"><b>Instant wallet, bookmark to save</b></li>
<li>
</li>
</ul>
</li>
<li></li>
</ul>
</div>
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
<!-- Messages: style can be found in dropdown.less-->
<li class="dropdown messages-menu">
</li>
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
<!-- Sidebar user panel -->
<!-- /.search form -->
<!-- sidebar menu: : style can be found in sidebar.less -->
<ul class="sidebar-menu">
<li class="header">MAIN NAVIGATIONs</li>
{% endblock %} {% block menuitems %}
<li class="active treeview">
<a href="#">
<i class="fa fa-bitcoin"></i> <span>Wallets</span>
<i class="fa fa-angle-left pull-right"></i>
</a>
<ul class="treeview-menu">
{% if len > 1 %}
{%for i in range(0, len)%}
<li><a href="wallet?wal={{ thearr[i][0] }}&usr={{ thearr[i][4] }}" ><i class="fa fa-bolt"></i> {{ thearr[i][3] }}</a></li>
{% for w in user_wallets %}
<li>
<a href="wallet?wal={{ w.id }}&usr={{ w.user }}"
><i class="fa fa-bolt"></i> {{ w.name }}</a
>
</li>
{% endfor %}
{% else %}
<li><a href="wallet?wal={{ thewal }}&usr={{ theid }}" ><i class="fa fa-bolt"></i> {{ walnme }}</a></li>
{% endif %}
<li><a onclick="sidebarmake()">Add a wallet +</a></li>
<div id="sidebarmake"></div>
</ul>
</li>
<li class="treeview">
<a href="#">
<i class="fa fa-th"></i> <span>Extensions</span> <small class="label pull-right bg-green">coming soon</small>
<i class="fa fa-th"></i> <span>Extensions</span>
<small class="label pull-right bg-green">coming soon</small>
</a>
</li>
</ul>
</section>
<!-- /.sidebar -->
</aside>
{% endblock %} {% block body %}
<!-- Right side column. Contains the navbar and content of the page -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Wallet
<small>Control panel <div id="wonga"></div></small>
<small
>Control panel
<div id="wonga"></div
></small>
</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> Home</a></li>
<li>
<a href="#"><i class="fa fa-dashboard"></i> Home</a>
</li>
<li class="active">Wallets</li>
<li href="wallet?wal={{thewal}}" class="active">{{ walnme }}</li>
<li href="wallet?wal={{ wallet.id }}" class="active">
{{ wallet.name }}
</li>
</ol>
</section>
<!-- Main content -->
@ -116,430 +66,517 @@
<a href="#" class="small-box-footer">
<div class="small-box bg-aqua">
<div class="inner">
<h3><b>{{ walbal }} sats</b></h3>
<h3>{{ walnme }}</h3>
<h3><b>{{ wallet.balance }} sats</b></h3>
<h3>{{ wallet.name }}</h3>
</div>
<div class="icon">
<i class="ion ion-flash"></i>
</div>
</a>
</div>
</div><!-- ./col -->
<!-- ./col -->
</div><!-- /.row -->
><!-- /.row -->
<div class="row">
<div class="col-sm-3"> <button onclick="sendfundsinput()" class="btn btn-block btn-primary btn-lg">Send</button></div>
<div class="col-sm-3"> <button onclick="receive()" class="btn btn-block btn-primary btn-lg">Receive</button></div>
<div class="col-sm-3">
<button
onclick="sendfundsinput()"
class="btn btn-block btn-primary btn-lg"
>
Send
</button>
</div>
<div class="col-sm-3">
<button
onclick="receive()"
class="btn btn-block btn-primary btn-lg"
>
Receive
</button>
</div>
</div>
<div id="receive"></div>
<div id="sendfunds"></div>
<div class="row">
<div class="col-md-6">
<div class="box">
<div class="box-header">
<h3 class="box-title">Transactions <b id="demo"></b></h3>
</div><!-- /.box-header -->
</div>
<!-- /.box-header -->
<div class="box-body no-padding">
<table id="pagnation" class="table table-bordered table-striped">
<table
id="pagnation"
class="table table-bordered table-striped"
>
<tr>
<th>Memo</th>
<th style='width: 20%'>date</th>
<th style='width: 20%'>amount</th>
<th style="width: 20%">date</th>
<th style="width: 20%">amount</th>
</tr>
<tbody id="transactions">
</tbody></table>
</div><!-- /.box-body -->
</div><!-- /.box -->
<tbody id="transactions"></tbody>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
<div id="satschart"></div>
<div class="row">
<div class="col-md-6">
<div class="box box-solid">
<div class="box-header with-border">
</div><!-- /.box-header -->
<div class="box-header with-border"></div>
<!-- /.box-header -->
<div class="box-body">
<div class="box-group" id="accordion">
<!-- we are adding the .panel class so bootstrap.js collapse plugin detects it -->
<div class="panel box box-primary">
<div class="box-header with-border">
<h4 class="box-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseThree" class="collapsed" aria-expanded="false">
Wallet "{{ walnme }}" API info
<a
data-toggle="collapse"
data-parent="#accordion"
href="#collapseThree"
class="collapsed"
aria-expanded="false"
>
Wallet "{{ wallet.name }}" API info
</a>
</h4>
</div>
<div id="collapseThree" class="panel-collapse collapse" aria-expanded="false">
<div class="box-body" style='word-wrap: break-word;'>
<b>Admin key: </b><i>{{ adminkey }}</i><br/>
<b>Invoice/Read key: </b><i>{{ inkey }}</i><br/>
Generate an invoice:<br/><code>POST /v1/invoices</code><br/>Header <code>{"Grpc-Metadata-macaroon": "<i>{{ inkey }}</i>"}</code><br/>
Body <code>{"value": "200","memo": "beer"} </code><br/>
Returns <code>{"pay_req": string,"pay_id": string} </code><br/>
*payment will not register in the wallet until the "check invoice" endpoint is used<br/><br/>
<div
id="collapseThree"
class="panel-collapse collapse"
aria-expanded="false"
>
<div class="box-body" style="word-wrap: break-word;">
<b>Admin key: </b><i>{{ wallet.adminkey }}</i><br />
<b>Invoice/Read key: </b><i>{{ wallet.inkey }}</i
><br />
Generate an invoice:<br /><code
>POST /v1/invoices</code
><br />Header
<code
>{"Grpc-Metadata-macaroon": "<i
>{{ wallet.inkey }}</i
>"}</code
><br />
Body <code>{"value": "200","memo": "beer"} </code
><br />
Returns
<code>{"pay_req": string,"pay_id": string} </code
><br />
*payment will not register in the wallet until the
"check invoice" endpoint is used<br /><br />
Check invoice:<br />
Check an invoice:<br/><code>GET /v1/invoice/*payment_hash*</code><br/>Header <code>{"Grpc-Metadata-macaroon": "<i>{{ inkey }}</i>"}</code><br/>
Returns <code>{"PAID": "TRUE"}/{"PAID": "FALSE"} </code><br/>
Check an invoice:<br /><code
>GET /v1/invoice/*payment_hash*</code
><br />Header
<code
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i
>"}</code
><br />
Returns
<code>{"PAID": "TRUE"}/{"PAID": "FALSE"} </code><br />
*if using LNTXBOT return will hang until paid<br /><br />
</div>
</div>
</div>
<div class="panel box box-danger">
<div class="box-header with-border">
<h4 class="box-title">
<a data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" class="collapsed" aria-expanded="false">
<a
data-toggle="collapse"
data-parent="#accordion"
href="#collapseTwo"
class="collapsed"
aria-expanded="false"
>
Delete wallet
</a>
</h4>
</div>
<div id="collapseTwo" class="panel-collapse collapse" aria-expanded="false">
<div
id="collapseTwo"
class="panel-collapse collapse"
aria-expanded="false"
>
<div class="box-body">
This whole wallet will be deleted, the funds will be UNRECOVERABLE <br/><br/><button class="btn btn-danger" onclick="deletewallet()">Delete wallet</button>
This whole wallet will be deleted, the funds will be
UNRECOVERABLE <br /><br /><button
class="btn btn-danger"
onclick="deletewallet()"
>
Delete wallet
</button>
</div>
</div>
</div>
</div>
</div><!-- /.box-body -->
</div><!-- /.box -->
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<footer class="main-footer">
<div class="pull-right hidden-xs">
<b>BETA</b>
<!-- /.content -->
</a>
</div>
</div>
</section>
</div>
<strong>Learn more about LNbits <a href="https://github.com/arcbtc/lnbits">https://github.com/arcbtc/lnbits</a>.</strong>
</footer>
</div><!-- ./wrapper -->
<link rel="stylesheet" media="screen" href = "{{ url_for('static', filename='plugins/bootstrap-wysihtml5/bootstrap3-wysihtml5.min.css') }}">
<script>
var inmacaroon = "{{ inkey }}";
var adminmacaroon = "{{ inkey }}";
var thehash = "";
var theinvoice = "";
var outamount = "";
var outmemo = "";
var inmacaroon = '{{ wallet.inkey }}'
var adminmacaroon = '{{ wallet.inkey }}'
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);
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;
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);
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;
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'>" +
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 = "";
'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("{{ walbal }}")){
document.getElementById("sendfunds").innerHTML = "<div class='row'><div class='col-md-6'>"+
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>"+
'</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>";
'</br/></br/></div></div>'
}
}
function receive() {
document.getElementById("receive").innerHTML = "<br/><div class='row'><div id='QRCODE'>" +
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='Send Ajax request' /></div>" +
"</div></div><br/>";
document.getElementById("sendfunds").innerHTML = "";
"<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}), '{{ 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"), {
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",
colorDark: '#000000',
colorLight: '#ffffff',
correctLevel: QRCode.CorrectLevel.M
});
getAjax('/v1/invoice/'+ thehash, '{{ inkey }}', function(datab){
console.log(JSON.parse(datab).PAID);
})
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=" + "{{thewal}}" + "&usr=" + "{{theid}}";
window.location.href =
'wallet?wal=' + '{{ wallet.id }}' + '&usr=' + '{{ user }}'
}
});
});
})
}
)
}
function cancelsend() {
window.location.href = "wallet?wal=" + "{{thewal}}" + "&usr=" + "{{theid}}";
window.location.href =
'wallet?wal=' + '{{ wallet.id }}' + '&usr=' + '{{ user }}'
}
function sendfunds(invoice) {
var url = '/v1/channels/transactions';
postAjax(url, JSON.stringify({'payment_request' : invoice}), '{{ adminkey }}', function(data){
thehash = JSON.parse(data).payment_hash;
console.log(JSON.parse(data));
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=" + "{{thewal}}" + "&usr=" + "{{theid}}";
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'>" +
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");
"<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();
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);
});
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..."
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);
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",
});
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("{{ walbal }}")){
document.getElementById("sendfunds").innerHTML = "<div class='row'><div class='col-md-6'>"+
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>"+
'</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>";
'</br/></br/></div></div>'
}
} else {
outputMessage.hidden = false
outputData.parentElement.hidden = true
}
else {
outputMessage.hidden = false;
outputData.parentElement.hidden = true;
}
requestAnimationFrame(tick)
}
requestAnimationFrame(tick);
}
}
function deletewallet() {
var urll = "deletewallet?wal={{ thewal }}&usr={{ theid }}";
window.location.href = urll;
var urll = 'deletewallet?wal={{ wallet.id }}&usr={{ user }}'
window.location.href = urll
}
function sidebarmake() {
document.getElementById("sidebarmake").innerHTML = "<li><div class='form-group'>"+
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/>";
'</div></li><br/><br/>'
}
function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var charactersLength = characters.length;
var result = ''
var characters =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
var charactersLength = characters.length
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
result += characters.charAt(Math.floor(Math.random() * charactersLength))
}
return result;
return result
}
function newwallet() {
walname = document.getElementById("walname").value;
window.location.href = "wallet?usr=" + "{{ theid }}" + "&wal=" + makeid(40) + "&nme=" + walname;
walname = document.getElementById('walname').value
window.location.href =
'wallet?usr=' + '{{ user }}' + '&wal=' + makeid(40) + '&nme=' + walname
}
var trans = "{{ transactions }}";
var transac = trans.substr(1);
var trans = '{{ transactions }}'
var transac = trans.substr(1)
//console.log(trans);
if(transac != ""){
var transactions = "";
var linechart = [];
var tran = transac.split("!");
tran.shift();
console.log(tran);
if (transac != '') {
var transactions = ''
var linechart = []
var tran = transac.split('!')
tran.shift()
console.log(tran)
for (var i = 0; i < tran.length; i++) {
rects = tran[i].split(",");
rectstime = String(rects[1]).split(".");
rects = tran[i].split(',')
rectstime = String(rects[1]).split('.')
var datime = convertTimestamp(rectstime[0])
//Make the transactions table
transactions += "<tr><td style='width: 50%'>" + rects[0] + "</td><td>" + datime + "</td><td>" + rects[2] + "</td></tr>";
transactions +=
"<tr><td style='width: 50%'>" +
rects[0] +
'</td><td>' +
datime +
'</td><td>' +
rects[2] +
'</td></tr>'
//Make the line chart
if (i == 0) {
linechart.push({y: datime, item1: rects[2]});
}
else{
linechart.push({y: datime, item1: rects[3]});
linechart.push({y: datime, item1: rects[2]})
} else {
linechart.push({y: datime, item1: rects[3]})
}
}
document.getElementById("transactions").innerHTML = transactions;
if(linechart[0] != ""){
document.getElementById("satschart").innerHTML = "<div class='row'><div class='col-md-6'><div class='box box-info'><div class='box-header'>" +
document.getElementById('transactions').innerHTML = transactions
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>";
"<div class='chart' id='line-chart' style='height: 300px;'></div></div></div></div></div>"
}
}
console.log(linechart);
console.log(linechart)
var data = linechart
var line = new Morris.Line({
element: 'line-chart',
@ -550,8 +587,7 @@ console.log(linechart);
labels: ['Item 1'],
lineColors: ['#3c8dbc'],
hideHover: 'auto'
});
})
function convertTimestamp(timestamp) {
var d = new Date(timestamp * 1000),
@ -562,10 +598,10 @@ function convertTimestamp(timestamp) {
h = hh,
min = ('0' + d.getMinutes()).slice(-2),
ampm = 'AM',
time;
time = yyyy + '-' + mm + '-' + dd + ' ' + h + ':' + min;
return time;
time
time = yyyy + '-' + mm + '-' + dd + ' ' + h + ':' + min
return time
}
</script>
{% endblock %}

Loading…
Cancel
Save