refactor: /wallet tweaks

This commit is contained in:
Eneko Illarramendi 2020-04-01 22:18:46 +02:00
parent d03785558b
commit 649cc888ab
15 changed files with 628 additions and 419 deletions

View file

@ -158,12 +158,6 @@ new Vue({
return (this.payments) return (this.payments)
? _.where(this.payments, {pending: 1}).length > 0 ? _.where(this.payments, {pending: 1}).length > 0
: false; : false;
},
paymentsFiltered: function () {
return this.payments;
return this.payments.filter(function (obj) {
return obj.isPaid;
});
} }
}, },
methods: { methods: {
@ -325,6 +319,14 @@ new Vue({
this.fetchPayments(true).then(function () { this.fetchPayments(true).then(function () {
dismissMsg(); dismissMsg();
}); });
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paymentsTable.columns, this.payments);
}
},
watch: {
'payments': function () {
EventHub.$emit('update-wallet-balance', [this.w.wallet.id, this.balance]);
} }
}, },
created: function () { created: function () {

View file

@ -57,10 +57,10 @@
</q-card-section> </q-card-section>
<q-card-actions align="right"> <q-card-actions align="right">
<q-btn flat <q-btn flat
color="deep-purple" color="grey"
type="a" href="https://github.com/arcbtc/lnbits" target="_blank" rel="noopener">View project in GitHub</q-btn> type="a" href="https://github.com/arcbtc/lnbits" target="_blank" rel="noopener">View project in GitHub</q-btn>
<q-btn flat <q-btn flat
color="deep-purple" color="grey"
type="a" href="https://paywall.link/to/f4e4e" target="_blank" rel="noopener">Donate</q-btn> type="a" href="https://paywall.link/to/f4e4e" target="_blank" rel="noopener">Donate</q-btn>
</q-card-actions> </q-card-actions>
</q-card> </q-card>

View file

@ -54,7 +54,7 @@
<h5 class="text-subtitle1 q-my-none">Transactions</h5> <h5 class="text-subtitle1 q-my-none">Transactions</h5>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-btn flat color="grey" onclick="exportbut()">Export to CSV</q-btn> <q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
<!--<q-btn v-if="pendingPaymentsExist" dense flat round icon="update" color="grey" @click="checkPendingPayments"> <!--<q-btn v-if="pendingPaymentsExist" dense flat round icon="update" color="grey" @click="checkPendingPayments">
<q-tooltip>Check pending</q-tooltip> <q-tooltip>Check pending</q-tooltip>
</q-btn>--> </q-btn>-->
@ -64,7 +64,7 @@
</div> </div>
</div> </div>
<q-table dense flat <q-table dense flat
:data="paymentsFiltered" :data="payments"
row-key="payhash" row-key="payhash"
:columns="paymentsTable.columns" :columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"> :pagination.sync="paymentsTable.pagination">
@ -122,40 +122,44 @@
label="API info" label="API info"
:content-inset-level="0.5" :content-inset-level="0.5"
> >
<q-expansion-item group="api" expand-separator label="Create an invoice"> <q-expansion-item group="api" dense expand-separator label="Create an invoice (incoming)">
<q-card> <q-card>
<q-card-section> <q-card-section>
Generate an invoice:<br /><code>POST /api/v1/invoices</code <code><span class="text-light-green">POST</span> /api/v1/payments</code>
><br />Header <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code <code
>{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i >{"Grpc-Metadata-macaroon": "<i>{{ wallet.inkey }}</i
>"}</code >"}</code
><br /> ><br />
Body <code>{"value": "200","memo": "beer"} </code><br /> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
Returns <code>{"out": false, "value": &lt;int&gt;, "memo": &lt;string&gt;}</code>
<code>{"pay_req": string,"pay_id": string} </code><br /> <h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
*payment will not register in the wallet until the "check <code>{"checking_id": &lt;string&gt;, "payment_request": &lt;string&gt;}</code>
invoice" endpoint is used<br /><br />
Check invoice:<br />
Check an invoice:<br /><code
>GET /api/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 />
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-expansion-item group="api" expand-separator label="Get an invoice"> <q-expansion-item group="api" dense expand-separator label="Pay an invoice (outgoing)">
<q-card> <q-card>
<q-card-section> <q-card-section>
This whole wallet will be deleted, the funds will be <strong>UNRECOVERABLE</strong>. <code><span class="text-light-green">POST</span> /api/v1/payments</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"Grpc-Metadata-macaroon": "{{ wallet.adminkey }}"}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code>{"out": true, "bolt11": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 201 CREATED (application/json)</h5>
<code>{"checking_id": &lt;string&gt;}</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Check an invoice (incoming or outgoing)"
class="q-mb-md">
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /api/v1/payments/&lt;checking_id&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"Grpc-Metadata-macaroon": "{{ wallet.inkey }}"}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Returns 200 OK (application/json)</h5>
<code>{"paid": &lt;bool&gt;}</code>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>

View file

@ -45,7 +45,7 @@ def api_payments_create_invoice():
return jsonify({"checking_id": checking_id, "payment_request": payment_request}), Status.CREATED return jsonify({"checking_id": checking_id, "payment_request": payment_request}), Status.CREATED
@api_check_wallet_macaroon(key_type="invoice") @api_check_wallet_macaroon(key_type="admin")
@api_validate_post_request(required_params=["bolt11"]) @api_validate_post_request(required_params=["bolt11"])
def api_payments_pay_invoice(): def api_payments_pay_invoice():
if not isinstance(g.data["bolt11"], str) or not g.data["bolt11"].strip(): if not isinstance(g.data["bolt11"], str) or not g.data["bolt11"].strip():

View file

@ -1,5 +1,7 @@
var LOCALE = 'en' var LOCALE = 'en'
var EventHub = new Vue();
var LNbits = { var LNbits = {
api: { api: {
request: function (method, url, macaroon, data) { request: function (method, url, macaroon, data) {
@ -20,7 +22,7 @@ var LNbits = {
}); });
}, },
payInvoice: function (wallet, bolt11) { payInvoice: function (wallet, bolt11) {
return this.request('post', '/api/v1/payments', wallet.inkey, { return this.request('post', '/api/v1/payments', wallet.adminkey, {
out: true, out: true,
bolt11: bolt11 bolt11: bolt11
}); });
@ -53,7 +55,7 @@ var LNbits = {
obj.wallets = obj.wallets.map(function (obj) { obj.wallets = obj.wallets.map(function (obj) {
return mapWallet(obj); return mapWallet(obj);
}).sort(function (a, b) { }).sort(function (a, b) {
return a.name > b.name; return a.name.localeCompare(b.name);
}); });
return obj; return obj;
}, },
@ -94,6 +96,44 @@ var LNbits = {
caption: [error.response.status, ' ', error.response.statusText].join('').toUpperCase() || null, caption: [error.response.status, ' ', error.response.statusText].join('').toUpperCase() || null,
icon: null icon: null
}); });
},
exportCSV: function (columns, data) {
var wrapCsvValue = function(val, formatFn) {
var formatted = formatFn !== void 0
? formatFn(val)
: val;
formatted = (formatted === void 0 || formatted === null)
? ''
: String(formatted);
formatted = formatted.split('"').join('""');
return `"${formatted}"`;
}
var content = [columns.map(function (col) {
return wrapCsvValue(col.label);
})].concat(data.map(function (row) {
return columns.map(function (col) {
return wrapCsvValue(
(typeof col.field === 'function')
? col.field(row)
: row[(col.field === void 0) ? col.name : col.field],
col.format
);
}).join(',');
})).join('\r\n');
var status = Quasar.utils.exportFile('table-export.csv', content, 'text/csv');
if (status !== true) {
Quasar.plugins.Notify.create({
message: 'Browser denied file download...',
color: 'negative',
icon: null
});
}
} }
} }
}; };

View file

@ -3,6 +3,7 @@ Vue.component('lnbits-wallet-list', {
return { return {
user: null, user: null,
activeWallet: null, activeWallet: null,
activeBalance: [],
showForm: false, showForm: false,
walletName: '' walletName: ''
} }
@ -10,7 +11,7 @@ Vue.component('lnbits-wallet-list', {
template: ` template: `
<q-list v-if="user && user.wallets.length" dense class="lnbits-drawer__q-list"> <q-list v-if="user && user.wallets.length" dense class="lnbits-drawer__q-list">
<q-item-label header>Wallets</q-item-label> <q-item-label header>Wallets</q-item-label>
<q-item v-for="wallet in user.wallets" :key="wallet.id" <q-item v-for="wallet in wallets" :key="wallet.id"
clickable clickable
:active="activeWallet && activeWallet.id == wallet.id" :active="activeWallet && activeWallet.id == wallet.id"
tag="a" :href="wallet.url"> tag="a" :href="wallet.url">
@ -25,7 +26,7 @@ Vue.component('lnbits-wallet-list', {
</q-item-section> </q-item-section>
<q-item-section> <q-item-section>
<q-item-label lines="1">{{ wallet.name }}</q-item-label> <q-item-label lines="1">{{ wallet.name }}</q-item-label>
<q-item-label caption>{{ wallet.fsat }} sat</q-item-label> <q-item-label caption>{{ wallet.live_fsat }} sat</q-item-label>
</q-item-section> </q-item-section>
<q-item-section side v-show="activeWallet && activeWallet.id == wallet.id"> <q-item-section side v-show="activeWallet && activeWallet.id == wallet.id">
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon> <q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
@ -41,7 +42,7 @@ Vue.component('lnbits-wallet-list', {
</q-item> </q-item>
<q-item v-if="showForm"> <q-item v-if="showForm">
<q-item-section> <q-item-section>
<q-form> <q-form @submit="createWallet">
<q-input filled dense v-model="walletName" label="Name wallet *"> <q-input filled dense v-model="walletName" label="Name wallet *">
<template v-slot:append> <template v-slot:append>
<q-btn round dense flat icon="send" size="sm" @click="createWallet" :disable="walletName == ''"></q-btn> <q-btn round dense flat icon="send" size="sm" @click="createWallet" :disable="walletName == ''"></q-btn>
@ -52,9 +53,23 @@ Vue.component('lnbits-wallet-list', {
</q-item> </q-item>
</q-list> </q-list>
`, `,
computed: {
wallets: function () {
var bal = this.activeBalance;
return this.user.wallets.map(function (obj) {
obj.live_fsat = (bal.length && bal[0] == obj.id)
? LNbits.utils.formatSat(bal[1])
: obj.fsat;
return obj;
});
}
},
methods: { methods: {
createWallet: function () { createWallet: function () {
LNbits.href.createWallet(this.walletName, this.user.id); LNbits.href.createWallet(this.walletName, this.user.id);
},
updateWalletBalance: function (payload) {
this.activeBalance = payload;
} }
}, },
created: function () { created: function () {
@ -64,6 +79,7 @@ Vue.component('lnbits-wallet-list', {
if (window.wallet) { if (window.wallet) {
this.activeWallet = LNbits.map.wallet(window.wallet); this.activeWallet = LNbits.map.wallet(window.wallet);
} }
EventHub.$on('update-wallet-balance', this.updateWalletBalance);
} }
}); });
@ -113,8 +129,9 @@ Vue.component('lnbits-extension-list', {
this.extensions = window.extensions.map(function (data) { this.extensions = window.extensions.map(function (data) {
return LNbits.map.extension(data); return LNbits.map.extension(data);
}).sort(function (a, b) { }).sort(function (a, b) {
return a.name > b.name; 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

@ -1,5 +1,5 @@
/*! /*!
* Quasar Framework v1.9.7 * Quasar Framework v1.9.12
* (c) 2015-present Razvan Stoenescu * (c) 2015-present Razvan Stoenescu
* Released under the MIT License. * Released under the MIT License.
*/ */

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +1,6 @@
/** /**
* vuex v3.1.2 * vuex v3.1.3
* (c) 2019 Evan You * (c) 2020 Evan You
* @license MIT * @license MIT
*/ */
(function (global, factory) { (function (global, factory) {
@ -398,7 +398,10 @@
handler(payload); handler(payload);
}); });
}); });
this._subscribers.forEach(function (sub) { return sub(mutation, this$1.state); });
this._subscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.forEach(function (sub) { return sub(mutation, this$1.state); });
if ( if (
options && options.silent options && options.silent
@ -429,6 +432,7 @@
try { try {
this._actionSubscribers this._actionSubscribers
.slice() // shallow copy to prevent iterator invalidation if subscriber synchronously calls unsubscribe
.filter(function (sub) { return sub.before; }) .filter(function (sub) { return sub.before; })
.forEach(function (sub) { return sub.before(action, this$1.state); }); .forEach(function (sub) { return sub.before(action, this$1.state); });
} catch (e) { } catch (e) {
@ -797,9 +801,7 @@
} }
function getNestedState (state, path) { function getNestedState (state, path) {
return path.length return path.reduce(function (state, key) { return state[key]; }, state)
? path.reduce(function (state, key) { return state[key]; }, state)
: state
} }
function unifyObjectStyle (type, payload, options) { function unifyObjectStyle (type, payload, options) {
@ -1042,7 +1044,7 @@
var index = { var index = {
Store: Store, Store: Store,
install: install, install: install,
version: '3.1.2', version: '3.1.3',
mapState: mapState, mapState: mapState,
mapMutations: mapMutations, mapMutations: mapMutations,
mapGetters: mapGetters, mapGetters: mapGetters,

File diff suppressed because one or more lines are too long

View file

@ -3,7 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Material+Icons" type="text/css"> <link rel="stylesheet" href="//fonts.googleapis.com/css?family=Material+Icons" type="text/css">
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='vendor/quasar@1.9.7/quasar.min.css') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='vendor/quasar@1.9.12/quasar.min.css') }}">
{% assets 'base_css' %} {% assets 'base_css' %}
<link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}"> <link rel="stylesheet" type="text/css" href="{{ ASSET_URL }}">
{% endassets %} {% endassets %}
@ -62,15 +62,15 @@
{% if DEBUG %} {% if DEBUG %}
<script src="{{ url_for('static', filename='vendor/vue@2.6.11/vue.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vue@2.6.11/vue.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/vue-router@3.1.6/vue-router.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vue-router@3.1.6/vue-router.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/vuex@3.1.2/vuex.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vuex@3.1.3/vuex.js') }}"></script>
<script src="{{ url_for('static', filename='vendor/quasar@1.9.7/quasar.umd.js') }}"></script> <script src="{{ url_for('static', filename='vendor/quasar@1.9.12/quasar.umd.js') }}"></script>
{% else %} {% else %}
{% assets output='__bundle__/vue.js', {% assets output='__bundle__/vue.js',
'vendor/quasar@1.9.7/quasar.ie.polyfills.umd.min.js', 'vendor/quasar@1.9.12/quasar.ie.polyfills.umd.min.js',
'vendor/vue@2.6.11/vue.min.js', 'vendor/vue@2.6.11/vue.min.js',
'vendor/vue-router@3.1.6/vue-router.min.js', 'vendor/vue-router@3.1.6/vue-router.min.js',
'vendor/vuex@3.1.2/vuex.min.js', 'vendor/vuex@3.1.3/vuex.min.js',
'vendor/quasar@1.9.7/quasar.umd.min.js' %} 'vendor/quasar@1.9.12/quasar.umd.min.js' %}
<script type="text/javascript" src="{{ ASSET_URL }}"></script> <script type="text/javascript" src="{{ ASSET_URL }}"></script>
{% endassets %} {% endassets %}
{% endif %} {% endif %}