chore: various tweaks

This commit is contained in:
Eneko Illarramendi 2020-04-16 17:10:53 +02:00
parent 490e166f75
commit dd23b20090
25 changed files with 156 additions and 99 deletions

View File

@ -1,20 +1,31 @@
FLASK_APP=lnbits FLASK_APP=lnbits
FLASK_ENV=development FLASK_ENV=development
LNBITS_SITE_TITLE=LNbits
LNBITS_WITH_ONION=0 LNBITS_WITH_ONION=0
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet" LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
LNBITS_FEE_RESERVE=0 LNBITS_FEE_RESERVE=0
LNBITS_BACKEND_WALLET_CLASS="LntxbotWallet" LNBITS_BACKEND_WALLET_CLASS=LntxbotWallet
CLIGHTNING_RPC="/home/arc/.lightning/bitcoin/lightning-rpc"
LNBITS_ENDPOINT=127.0.0.1:5000
LNBITS_INVOICE_MACAROON=LNBITS_INVOICE_MACAROON
LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON
LND_GRPC_ENDPOINT=127.0.0.1 LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=11009 LND_GRPC_PORT=11009
LND_CERT='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert' LND_CERT="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_ADMIN_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon' LND_ADMIN_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon"
LND_INVOICE_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon' LND_INVOICE_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/invoice.macaroon"
LND_READ_MACAROON='/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon' LND_READ_MACAROON="/home/arc/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/read.macaroon"
CLIGHTNING_RPC='/home/arc/.lightning/bitcoin/lightning-rpc' LNPAY_API_ENDPOINT=https://lnpay.co/v1/
LNPAY_API_KEY=LNPAY_API_KEY
LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY
LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY
LNPAY_READ_KEY=LNPAY_READ_KEY
LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/ LNTXBOT_API_ENDPOINT=https://lntxbot.bigsun.xyz/
LNTXBOT_ADMIN_KEY=LNTXBOT_ADMIN_KEY LNTXBOT_ADMIN_KEY=LNTXBOT_ADMIN_KEY
@ -23,13 +34,3 @@ LNTXBOT_INVOICE_KEY=LNTXBOT_INVOICE_KEY
OPENNODE_API_ENDPOINT=https://api.opennode.com/ OPENNODE_API_ENDPOINT=https://api.opennode.com/
OPENNODE_ADMIN_KEY=OPENNODE_ADMIN_KEY OPENNODE_ADMIN_KEY=OPENNODE_ADMIN_KEY
OPENNODE_INVOICE_KEY=OPENNODE_INVOICE_KEY OPENNODE_INVOICE_KEY=OPENNODE_INVOICE_KEY
LNPAY_API_ENDPOINT=https://lnpay.co/v1/
LNPAY_API_KEY=LNPAY_API_KEY
LNPAY_ADMIN_KEY=LNPAY_ADMIN_KEY
LNPAY_INVOICE_KEY=LNPAY_INVOICE_KEY
LNPAY_READ_KEY=LNPAY_READ_KEY
LNBITS_ENDPOINT=127.0.0.1:5000
LNBITS_INVOICE_MACAROON=LNBITS_INVOICE_MACAROON
LNBITS_ADMIN_MACAROON=LNBITS_ADMIN_MACAROON

View File

@ -23,6 +23,7 @@ gunicorn = "*"
pylightning = "*" pylightning = "*"
pyscss = "*" pyscss = "*"
requests = "*" requests = "*"
shortuuid = "*"
[dev-packages] [dev-packages]
black = "==19.10b0" black = "==19.10b0"

View File

@ -58,6 +58,7 @@ for ext in valid_extensions:
app.jinja_env.globals["DEBUG"] = app.config["DEBUG"] app.jinja_env.globals["DEBUG"] = app.config["DEBUG"]
app.jinja_env.globals["EXTENSIONS"] = valid_extensions app.jinja_env.globals["EXTENSIONS"] = valid_extensions
app.jinja_env.globals["SITE_TITLE"] = getenv("LNBITS_SITE_TITLE", "LNbits")
app.jinja_env.filters["megajson"] = megajson app.jinja_env.filters["megajson"] = megajson

View File

@ -1,5 +1,5 @@
from uuid import uuid4
from typing import List, Optional from typing import List, Optional
from uuid import uuid4
from lnbits.db import open_db from lnbits.db import open_db
from lnbits.settings import DEFAULT_WALLET_NAME, FEE_RESERVE from lnbits.settings import DEFAULT_WALLET_NAME, FEE_RESERVE

View File

@ -1,9 +1,10 @@
from typing import Tuple from typing import Optional, Tuple
from lnbits.bolt11 import decode as bolt11_decode from lnbits.bolt11 import decode as bolt11_decode
from lnbits.settings import WALLET, FEE_RESERVE from lnbits.settings import WALLET, FEE_RESERVE
from .crud import create_payment from .crud import create_payment
from .models import Wallet
def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str]: def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str]:
@ -21,14 +22,23 @@ def create_invoice(*, wallet_id: str, amount: int, memo: str) -> Tuple[str, str]
return checking_id, payment_request return checking_id, payment_request
def pay_invoice(*, wallet_id: str, bolt11: str) -> str: def pay_invoice(*, wallet: Wallet, bolt11: str, max_sat: Optional[int] = None) -> str:
try: try:
invoice = bolt11_decode(bolt11) invoice = bolt11_decode(bolt11)
ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11) ok, checking_id, fee_msat, error_message = WALLET.pay_invoice(bolt11)
if invoice.amount_msat == 0:
raise ValueError("Amountless invoices not supported.")
if max_sat and invoice.amount_msat > max_sat * 1000:
raise ValueError("Amount in invoice is too high.")
if invoice.amount_msat > wallet.balance_msat:
raise PermissionError("Insufficient balance.")
if ok: if ok:
create_payment( create_payment(
wallet_id=wallet_id, wallet_id=wallet.id,
checking_id=checking_id, checking_id=checking_id,
amount=-invoice.amount_msat, amount=-invoice.amount_msat,
memo=invoice.description, memo=invoice.description,
@ -42,3 +52,7 @@ def pay_invoice(*, wallet_id: str, bolt11: str) -> str:
raise Exception(error_message or "Unexpected backend error.") raise Exception(error_message or "Unexpected backend error.")
return checking_id return checking_id
def check_payment(*, checking_id: str) -> str:
pass

View File

@ -35,7 +35,7 @@
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Check an invoice (incoming or outgoing)" <q-expansion-item group="api" dense expand-separator label="Check an invoice (incoming or outgoing)"
class="q-mb-md"> class="q-pb-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<code><span class="text-light-blue">GET</span> /api/v1/payments/&lt;checking_id&gt;</code> <code><span class="text-light-blue">GET</span> /api/v1/payments/&lt;checking_id&gt;</code>

View File

@ -8,12 +8,9 @@
{% endassets %} {% endassets %}
{% endblock %} {% endblock %}
{% block drawer %}
{% endblock %}
{% block page %} {% block page %}
<div class="row q-col-gutter-md justify-between"> <div class="row q-col-gutter-md justify-between">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>

View File

@ -9,6 +9,7 @@
{% block scripts %} {% block scripts %}
{{ window_vars(user, wallet) }} {{ window_vars(user, wallet) }}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
{% assets filters='rjsmin', output='__bundle__/core/chart.js', {% assets filters='rjsmin', output='__bundle__/core/chart.js',
'vendor/moment@2.24.0/moment.min.js', 'vendor/moment@2.24.0/moment.min.js',
'vendor/chart.js@2.9.3/chart.min.js' %} 'vendor/chart.js@2.9.3/chart.min.js' %}
@ -17,7 +18,6 @@
{% assets filters='rjsmin', output='__bundle__/core/wallet.js', {% assets filters='rjsmin', output='__bundle__/core/wallet.js',
'vendor/bolt11/utils.js', 'vendor/bolt11/utils.js',
'vendor/bolt11/decoder.js', 'vendor/bolt11/decoder.js',
'vendor/vue-qrcode@1.0.2/vue-qrcode.min.js',
'vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js', 'vendor/vue-qrcode-reader@2.1.1/vue-qrcode-reader.min.js',
'core/js/wallet.js' %} 'core/js/wallet.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
@ -26,7 +26,7 @@
{% block page %} {% block page %}
<div class="row q-col-gutter-md"> <div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<h3 class="q-my-none"><strong>{% raw %}{{ fbalance }}{% endraw %}</strong> sat</h3> <h3 class="q-my-none"><strong>{% raw %}{{ fbalance }}{% endraw %}</strong> sat</h3>
@ -105,9 +105,10 @@
</q-card> </q-card>
</div> </div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md"> <div class="col-12 col-md-5 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-btn flat color="grey" @click="exportCSV" class="float-right">Renew keys</q-btn>
<h6 class="text-subtitle1 q-mt-none q-mb-sm">LNbits wallet</h6> <h6 class="text-subtitle1 q-mt-none q-mb-sm">LNbits wallet</h6>
<strong>Wallet name: </strong><em>{{ wallet.name }}</em><br> <strong>Wallet name: </strong><em>{{ wallet.name }}</em><br>
<strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br> <strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br>
@ -139,12 +140,12 @@
</div> </div>
<q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog"> <q-dialog v-model="receive.show" position="top" @hide="closeReceiveDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> <q-card v-if="!receive.paymentReq" class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form v-if="!receive.paymentReq" class="q-gutter-md"> <q-form class="q-gutter-md">
<q-input filled dense <q-input filled dense
v-model.number="receive.data.amount" v-model.number="receive.data.amount"
type="number" type="number"
label="Amount *"></q-input> label="Amount (sat) *"></q-input>
<q-input filled dense <q-input filled dense
v-model.trim="receive.data.memo" v-model.trim="receive.data.memo"
label="Memo" label="Memo"
@ -158,26 +159,24 @@
</div> </div>
<q-spinner v-if="receive.status == 'loading'" color="deep-purple" size="2.55em"></q-spinner> <q-spinner v-if="receive.status == 'loading'" color="deep-purple" size="2.55em"></q-spinner>
</q-form> </q-form>
<div v-else> </q-card>
<div class="text-center q-mb-md"> <q-card v-else class="q-pa-lg lnbits__dialog-card">
<a :href="'lightning:' + receive.paymentReq"> <div class="text-center q-mb-md">
<a :href="'lightning:' + receive.paymentReq">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode :value="receive.paymentReq" :options="{width: 340}" class="rounded-borders"></qrcode> <qrcode :value="receive.paymentReq" :options="{width: 340}" class="rounded-borders"></qrcode>
</a> </q-responsive>
</div> </a>
<!--<q-separator class="q-my-md"></q-separator> </div>
<p class="text-caption" style="word-break: break-all"> <div class="row justify-between">
{% raw %}{{ receive.paymentReq }}{% endraw %} <q-btn flat color="grey" @click="copyText(receive.paymentReq)">Copy invoice</q-btn>
</p>--> <q-btn v-close-popup flat color="grey">Close</q-btn>
<div class="row justify-between">
<q-btn flat color="grey" @click="copyText(receive.paymentReq)">Copy invoice</q-btn>
<q-btn v-close-popup flat color="grey">Close</q-btn>
</div>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog v-model="send.show" position="top" @hide="closeSendDialog"> <q-dialog v-model="send.show" position="top" @hide="closeSendDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div v-if="!send.invoice"> <div v-if="!send.invoice">
<q-form v-if="!sendCamera.show" class="q-gutter-md"> <q-form v-if="!sendCamera.show" class="q-gutter-md">
<q-input filled dense <q-input filled dense

View File

@ -1,12 +1,11 @@
from flask import g, jsonify, request from flask import g, jsonify, request
from lnbits.bolt11 import decode as bolt11_decode
from lnbits.core import core_app from lnbits.core import core_app
from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request
from lnbits.helpers import Status from lnbits.helpers import Status
from lnbits.settings import WALLET from lnbits.settings import WALLET
from ..utils import create_invoice, pay_invoice from ..services import create_invoice, pay_invoice
@core_app.route("/api/v1/payments", methods=["GET"]) @core_app.route("/api/v1/payments", methods=["GET"])
@ -44,16 +43,11 @@ def api_payments_create_invoice():
@api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}}) @api_validate_post_request(schema={"bolt11": {"type": "string", "empty": False, "required": True}})
def api_payments_pay_invoice(): def api_payments_pay_invoice():
try: try:
invoice = bolt11_decode(g.data["bolt11"]) checking_id = pay_invoice(wallet=g.wallet, bolt11=g.data["bolt11"])
except ValueError as e:
if invoice.amount_msat == 0: return jsonify({"message": str(e)}), Status.BAD_REQUEST
return jsonify({"message": "Amountless invoices not supported."}), Status.BAD_REQUEST except PermissionError as e:
return jsonify({"message": str(e)}), Status.FORBIDDEN
if invoice.amount_msat > g.wallet.balance_msat:
return jsonify({"message": "Insufficient balance."}), Status.FORBIDDEN
checking_id = pay_invoice(wallet_id=g.wallet.id, bolt11=g.data["bolt11"])
except Exception as e: except Exception as e:
return jsonify({"message": str(e)}), Status.INTERNAL_SERVER_ERROR return jsonify({"message": str(e)}), Status.INTERNAL_SERVER_ERROR

View File

@ -2,5 +2,5 @@
"name": "AMilk", "name": "AMilk",
"short_description": "Assistant Faucet Milker", "short_description": "Assistant Faucet Milker",
"icon": "room_service", "icon": "room_service",
"contributors": ["eillarra"] "contributors": ["arcbtc"]
} }

View File

@ -80,7 +80,7 @@
</div> </div>
<q-dialog v-model="amilkDialog.show" position="top"> <q-dialog v-model="amilkDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form class="q-gutter-md"> <q-form class="q-gutter-md">
<q-select filled dense emit-value v-model="amilkDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *"> <q-select filled dense emit-value v-model="amilkDialog.data.wallet" :options="g.user.walletOptions" label="Wallet *">
</q-select> </q-select>

View File

@ -0,0 +1,6 @@
{
"name": "Events",
"short_description": "LN tickets for events.",
"icon": "local_activity",
"contributors": ["arcbtc"]
}

View File

@ -0,0 +1,5 @@
from lnbits.db import open_ext_db
def migrate():
print("pending")

View File

@ -1,15 +1,14 @@
from base64 import urlsafe_b64encode
from uuid import uuid4
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.db import open_ext_db from lnbits.db import open_ext_db
from lnbits.helpers import urlsafe_short_hash
from .models import Paywall from .models import Paywall
def create_paywall(*, wallet_id: str, url: str, memo: str, amount: int) -> Paywall: def create_paywall(*, wallet_id: str, url: str, memo: str, amount: int) -> Paywall:
with open_ext_db("paywall") as db: with open_ext_db("paywall") as db:
paywall_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') paywall_id = urlsafe_short_hash()
db.execute( db.execute(
""" """
INSERT INTO paywalls (id, wallet, url, memo, amount) INSERT INTO paywalls (id, wallet, url, memo, amount)

View File

@ -1,15 +1,14 @@
from base64 import urlsafe_b64encode
from uuid import uuid4
from typing import List, Optional, Union from typing import List, Optional, Union
from lnbits.db import open_ext_db from lnbits.db import open_ext_db
from lnbits.helpers import urlsafe_short_hash
from .models import TPoS from .models import TPoS
def create_tpos(*, wallet_id: str, name: str, currency: str) -> TPoS: def create_tpos(*, wallet_id: str, name: str, currency: str) -> TPoS:
with open_ext_db("tpos") as db: with open_ext_db("tpos") as db:
tpos_id = urlsafe_b64encode(uuid4().bytes_le).decode('utf-8') tpos_id = urlsafe_short_hash()
db.execute( db.execute(
""" """
INSERT INTO tposs (id, wallet, name, currency) INSERT INTO tposs (id, wallet, name, currency)

View File

@ -1,12 +1,12 @@
from flask import g, abort, render_template
from lnbits.core.crud import get_wallet
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.extensions.tpos import tpos_ext
from lnbits.helpers import Status
import requests import requests
import json
from flask import g, abort, render_template
from lnbits.core.crud import get_wallet
from lnbits.decorators import check_user_exists, validate_uuids
from lnbits.helpers import Status
from lnbits.extensions.tpos import tpos_ext
from .crud import get_tpos from .crud import get_tpos

View File

@ -1,14 +1,15 @@
from flask import g, jsonify, request from flask import g, jsonify, request
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.core.utils import create_invoice from lnbits.core.services import create_invoice
from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request from lnbits.decorators import api_check_wallet_macaroon, api_validate_post_request
from lnbits.helpers import Status from lnbits.helpers import Status
from lnbits.settings import WALLET, FEE_RESERVE from lnbits.settings import WALLET
from lnbits.extensions.tpos import tpos_ext
from lnbits.extensions.tpos import tpos_ext
from .crud import create_tpos, get_tpos, get_tposs, delete_tpos from .crud import create_tpos, get_tpos, get_tposs, delete_tpos
@tpos_ext.route("/api/v1/tposs", methods=["GET"]) @tpos_ext.route("/api/v1/tposs", methods=["GET"])
@api_check_wallet_macaroon(key_type="invoice") @api_check_wallet_macaroon(key_type="invoice")
def api_tposs(): def api_tposs():

View File

@ -1,5 +1,6 @@
import json import json
import os import os
import shortuuid
import sqlite3 import sqlite3
from typing import List, NamedTuple, Optional from typing import List, NamedTuple, Optional
@ -52,6 +53,10 @@ class Status:
INTERNAL_SERVER_ERROR = 500 INTERNAL_SERVER_ERROR = 500
def urlsafe_short_hash() -> str:
return shortuuid.uuid()
class MegaEncoder(json.JSONEncoder): class MegaEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
if isinstance(obj, sqlite3.Row): if isinstance(obj, sqlite3.Row):

View File

@ -29,12 +29,15 @@
color: inherit; color: inherit;
font-weight: bold; } font-weight: bold; }
.q-table--dense th:first-child, .q-table--dense td:first-child, .lnbits__dialog-card {
.q-table--dense .q-table__bottom { width: 500px; }
padding-left: 6px !important; }
.q-table--dense th:last-child, .q-table--dense td:last-child, .q-table--dense th:first-child, .q-table--dense td:first-child,
.q-table--dense .q-table__bottom { .q-table--dense .q-table__bottom {
padding-right: 6px !important; } padding-left: 6px !important; }
.q-table--dense th:last-child, .q-table--dense td:last-child,
.q-table--dense .q-table__bottom {
padding-right: 6px !important; }
video { video {
border-radius: 3px; } border-radius: 3px; }

View File

@ -91,13 +91,17 @@ Vue.component('lnbits-extension-list', {
} }
}, },
template: ` template: `
<q-list v-if="user" dense class="lnbits-drawer__q-list"> <q-list v-if="user && extensions.length" dense class="lnbits-drawer__q-list">
<q-item-label header>Extensions</q-item-label> <q-item-label header>Extensions</q-item-label>
<q-item v-for="extension in userExtensions" :key="extension.code" <q-item v-for="extension in userExtensions" :key="extension.code"
clickable clickable
:active="extension.isActive"
tag="a" :href="[extension.url, '?usr=', user.id].join('')"> tag="a" :href="[extension.url, '?usr=', user.id].join('')">
<q-item-section side> <q-item-section side>
<q-avatar size="md" color="grey-5"> <q-avatar size="md"
:color="(extension.isActive)
? (($q.dark.isActive) ? 'deep-purple-5' : 'deep-purple')
: 'grey-5'">
<q-icon :name="extension.icon" :size="($q.dark.isActive) ? '21px' : '20px'" <q-icon :name="extension.icon" :size="($q.dark.isActive) ? '21px' : '20px'"
:color="($q.dark.isActive) ? 'blue-grey-10' : 'grey-3'"></q-icon> :color="($q.dark.isActive) ? 'blue-grey-10' : 'grey-3'"></q-icon>
</q-avatar> </q-avatar>
@ -105,6 +109,9 @@ Vue.component('lnbits-extension-list', {
<q-item-section> <q-item-section>
<q-item-label lines="1">{{ extension.name }}</q-item-label> <q-item-label lines="1">{{ extension.name }}</q-item-label>
</q-item-section> </q-item-section>
<q-item-section side v-show="extension.isActive">
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
</q-item-section>
</q-item> </q-item>
<q-item clickable tag="a" :href="['/extensions?usr=', user.id].join('')"> <q-item clickable tag="a" :href="['/extensions?usr=', user.id].join('')">
<q-item-section side> <q-item-section side>
@ -119,18 +126,26 @@ Vue.component('lnbits-extension-list', {
computed: { computed: {
userExtensions: function () { userExtensions: function () {
if (!this.user) return []; if (!this.user) return [];
var path = window.location.pathname;
var userExtensions = this.user.extensions; var userExtensions = this.user.extensions;
return this.extensions.filter(function (obj) { return this.extensions.filter(function (obj) {
return userExtensions.indexOf(obj.code) !== -1; return userExtensions.indexOf(obj.code) !== -1;
}).map(function (obj) {
obj.isActive = path.startsWith(obj.url);
return obj;
}); });
} }
}, },
created: function () { created: function () {
this.extensions = window.extensions.map(function (data) { if (window.extensions) {
return LNbits.map.extension(data); this.extensions = window.extensions.map(function (data) {
}).sort(function (a, b) { return LNbits.map.extension(data);
return a.name.localeCompare(b.name); }).sort(function (a, b) {
}); return a.name.localeCompare(b.name);
});
}
if (window.user) { if (window.user) {
this.user = LNbits.map.user(window.user); this.user = LNbits.map.user(window.user);

View File

@ -48,6 +48,10 @@ body.body--dark .q-field--error {
} }
} }
.lnbits__dialog-card {
width: 500px;
}
.q-table--dense { .q-table--dense {
th:first-child, th:first-child,
td:first-child, td:first-child,

View File

@ -8,7 +8,11 @@
<link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}"> <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
{% endassets %} {% endassets %}
{% block styles %}{% endblock %} {% block styles %}{% endblock %}
<title>{% block title %}LNbits{% endblock %}</title> <title>
{% block title %}
{% if SITE_TITLE != 'LNbits' %}{{ SITE_TITLE }}{% else %}LNbits{% endif %}
{% endblock %}
</title>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% block head_scripts %}{% endblock %} {% block head_scripts %}{% endblock %}
@ -19,23 +23,31 @@
<q-header bordered class="bg-lnbits-dark"> <q-header bordered class="bg-lnbits-dark">
<q-toolbar> <q-toolbar>
<q-btn dense flat round icon="menu" @click="g.visibleDrawer = !g.visibleDrawer"></q-btn> {% block drawer_toggle %}
<q-toolbar-title><strong>LN</strong>bits</q-toolbar-title> <q-btn dense flat round icon="menu" @click="g.visibleDrawer = !g.visibleDrawer"></q-btn>
<q-badge color="yellow" text-color="black"> {% endblock %}
<span><span v-show="$q.screen.gt.sm">USE WITH CAUTION - LNbits wallet is still in </span>BETA</span> {% if SITE_TITLE != 'LNbits' %}
</q-badge> <q-toolbar-title>{{ SITE_TITLE }}</q-toolbar-title>
{% else %}
<q-toolbar-title><strong>LN</strong>bits</q-toolbar-title>
{% endif %}
{% block beta %}
<q-badge color="yellow" text-color="black">
<span><span v-show="$q.screen.gt.sm">USE WITH CAUTION - LNbits wallet is still in </span>BETA</span>
</q-badge>
{% endblock %}
<q-btn dense flat round @click="toggleDarkMode" :icon="($q.dark.isActive) ? 'brightness_3' : 'wb_sunny'" class="q-ml-lg" size="sm"> <q-btn dense flat round @click="toggleDarkMode" :icon="($q.dark.isActive) ? 'brightness_3' : 'wb_sunny'" class="q-ml-lg" size="sm">
<q-tooltip>Toggle Dark Mode</q-tooltip> <q-tooltip>Toggle Dark Mode</q-tooltip>
</q-btn> </q-btn>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
<q-drawer v-model="g.visibleDrawer" side="left" :width="($q.screen.lt.md) ? 260 : 230" show-if-above :elevated="$q.screen.lt.md"> {% block drawer %}
{% block drawer %} <q-drawer v-model="g.visibleDrawer" side="left" :width="($q.screen.lt.md) ? 260 : 230" show-if-above :elevated="$q.screen.lt.md">
<lnbits-wallet-list></lnbits-wallet-list> <lnbits-wallet-list></lnbits-wallet-list>
<lnbits-extension-list class="q-pb-xl"></lnbits-extension-list> <lnbits-extension-list class="q-pb-xl"></lnbits-extension-list>
{% endblock %} </q-drawer>
</q-drawer> {% endblock %}
<q-page-container> <q-page-container>
<q-page class="q-px-md q-py-lg" :class="{'q-px-lg': $q.screen.gt.xs}"> <q-page class="q-px-md q-py-lg" :class="{'q-px-lg': $q.screen.gt.xs}">

View File

@ -56,5 +56,5 @@ class OpenNodeWallet(Wallet):
if not r.ok: if not r.ok:
return PaymentStatus(None) return PaymentStatus(None)
statuses = {"pending": None, "confirmed": True, "error": False, "failed": False} statuses = {"initial": None, "pending": None, "confirmed": True, "error": False, "failed": False}
return PaymentStatus(statuses[r.json()["data"]["status"]]) return PaymentStatus(statuses[r.json()["data"]["status"]])

View File

@ -18,7 +18,7 @@ grpcio==1.28.1
gunicorn==20.0.4 gunicorn==20.0.4
idna==2.9 idna==2.9
itsdangerous==1.1.0 itsdangerous==1.1.0
jinja2==2.11.1 jinja2==2.11.2
limits==1.5.1 limits==1.5.1
lnd-grpc==0.4.0 lnd-grpc==0.4.0
lnurl==0.3.3 lnurl==0.3.3
@ -28,6 +28,7 @@ pydantic==1.4
pylightning==0.0.7.3 pylightning==0.0.7.3
pyscss==1.3.7 pyscss==1.3.7
requests==2.23.0 requests==2.23.0
shortuuid==1.0.1
six==1.14.0 six==1.14.0
typing-extensions==3.7.4.2 ; python_version < '3.8' typing-extensions==3.7.4.2 ; python_version < '3.8'
urllib3==1.25.8 urllib3==1.25.8