feat: add payments table to user manager (#2491)

* feat: add payments table to user manager

refactor payments table and payment chart into components and add them
to usermanager

* bundle
This commit is contained in:
dni ⚡ 2024-05-13 19:01:01 +02:00 committed by GitHub
parent 9933484558
commit a5623ef7c3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 592 additions and 546 deletions

View file

@ -102,226 +102,7 @@
</div>
</div>
</q-card>
<q-card
:style="$q.screen.lt.md ? {
background: $q.screen.lt.md ? 'none !important': ''
, boxShadow: $q.screen.lt.md ? 'none !important': ''
, marginTop: $q.screen.lt.md ? '0px !important': ''
} : ''"
>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h5
class="text-subtitle1 q-my-none"
:v-text="$t('transactions')"
></h5>
</div>
<div class="gt-sm col-auto">
<q-btn
flat
color="grey"
@click="exportCSV"
:label="$t('export_csv')"
></q-btn>
<q-btn
dense
flat
round
icon="show_chart"
color="grey"
@click="showChart"
>
<q-tooltip>
<span v-text="$t('chart_tooltip')"></span
></q-tooltip>
</q-btn>
</div>
</div>
<q-input
:style="$q.screen.lt.md ? {
display: mobileSimple ? 'none !important': ''
} : ''"
filled
dense
clearable
v-model="paymentsTable.search"
debounce="300"
:placeholder="$t('search_by_tag_memo_amount')"
class="q-mb-md"
>
</q-input>
<q-table
dense
flat
:data="paymentsOmitter"
:row-key="paymentTableRowKey"
:columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"
:no-data-label="$t('no_transactions')"
:filter="paymentsTable.search"
:loading="paymentsTable.loading"
:hide-header="mobileSimple"
:hide-bottom="mobileSimple"
@request="fetchPayments"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
v-text="col.label"
></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width class="text-center">
<q-icon
v-if="props.row.isPaid"
size="14px"
:name="props.row.isOut ? 'call_made' : 'call_received'"
:color="props.row.isOut ? 'pink' : 'green'"
@click="props.expand = !props.expand"
></q-icon>
<q-icon
v-else
name="settings_ethernet"
color="grey"
@click="props.expand = !props.expand"
>
<q-tooltip
><span v-text="$t('pending')"></span
></q-tooltip>
</q-icon>
</q-td>
<q-td
key="time"
:props="props"
style="white-space: normal; word-break: break-all"
>
<q-badge
v-if="props.row.tag"
color="yellow"
text-color="black"
>
<a
v-text="'#'+props.row.tag"
class="inherit"
:href="['/', props.row.tag].join('')"
></a>
</q-badge>
<span v-text="props.row.memo"></span>
<br />
<i>
<span v-text="props.row.dateFrom"></span>
<q-tooltip
><span v-text="props.row.date"></span
></q-tooltip>
</i>
</q-td>
<q-td
auto-width
key="amount"
v-if="'{{LNBITS_DENOMINATION}}' != 'sats'"
:props="props"
v-text="parseFloat(String(props.row.fsat).replaceAll(',', '')) / 100"
>
</q-td>
<q-td auto-width key="amount" v-else :props="props">
<span v-text="props.row.fsat"></span>
<br />
<i v-if="props.row.extra.wallet_fiat_currency">
<span
v-text="formatFiat(props.row.extra.wallet_fiat_currency, props.row.extra.wallet_fiat_amount)"
></span>
<br />
</i>
<i v-if="props.row.extra.fiat_currency">
<span
v-text="formatFiat(props.row.extra.fiat_currency, props.row.extra.fiat_amount)"
></span>
</i>
</q-td>
</q-tr>
<q-dialog v-model="props.expand" :props="props" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-lg">
<div v-if="props.row.isIn && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
<span v-text="$t('invoice_waiting')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
<div
v-if="props.row.bolt11"
class="text-center q-mb-lg"
>
<a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
<lnbits-qrcode
:value="'lightning:' + props.row.bolt11.toUpperCase()"
></lnbits-qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn
outline
color="grey"
@click="copyText(props.row.bolt11)"
:label="$t('copy_invoice')"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
:label="$t('close')"
></q-btn>
</div>
</div>
<div v-else-if="props.row.isPaid && props.row.isIn">
<q-icon
size="18px"
:name="'call_received'"
:color="'green'"
></q-icon>
<span v-text="$t('payment_received')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isPaid && props.row.isOut">
<q-icon
size="18px"
:name="'call_made'"
:color="'pink'"
></q-icon>
<span v-text="$t('payment_sent')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isOut && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
<span v-text="$t('outgoing_payment_pending')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
</div>
</q-card>
</q-dialog>
</template>
</q-table>
</q-card-section>
</q-card>
<payment-list :wallet="this.g.wallet" :mobileSimple="mobileSimple" />
</div>
{% if HIDE_API %}
<div class="col-12 col-md-4 q-gutter-y-md">
@ -877,27 +658,6 @@
</q-card>
</q-dialog>
<q-dialog v-model="paymentsChart.show" position="top">
<q-card class="q-pa-sm" style="width: 800px; max-width: unset">
<q-card-section>
<div class="row q-gutter-sm justify-between">
<div class="text-h6">Payments Chart</div>
<q-select
label="Group"
filled
dense
v-model="paymentsChart.group"
style="min-width: 120px"
:options="paymentsChart.groupOptions"
>
</q-select>
</div>
<canvas ref="canvas" width="600" height="400"></canvas>
</q-card-section>
</q-card>
</q-dialog>
<q-tabs
class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-top"
active-class="px-0"

View file

@ -1,6 +1,11 @@
<q-dialog v-model="walletDialog.show">
<q-card class="q-pa-lg" style="width: 700px; max-width: 80vw">
<h2 class="text-h6 q-mb-md">Wallets</h2>
<q-dialog v-model="paymentDialog.show">
<q-card class="q-pa-lg" style="width: 700px; max-width: 80vw">
<payment-list :wallet="activeWallet" />
</q-card>
</q-dialog>
<q-table :data="wallets" :columns="walletTable.columns">
<template v-slot:header="props">
<q-tr :props="props">
@ -17,6 +22,15 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
round
icon="menu"
size="sm"
color="secondary"
@click="showPayments(props.row.id)"
>
<q-tooltip>Show Payments</q-tooltip>
</q-btn>
<q-btn
v-if="!props.row.deleted"
round

View file

@ -49,7 +49,7 @@ include "users/_createWalletDialog.html" %}
<q-td>
<q-btn
round
icon="menu"
icon="list"
size="sm"
color="secondary"
@click="fetchWallets(props.row.id)"

View file

@ -13,16 +13,28 @@ from lnbits.core.crud import (
get_wallets,
update_admin_settings,
)
from lnbits.core.models import Account, AccountFilters, CreateTopup, User, Wallet
from lnbits.core.models import (
Account,
AccountFilters,
CreateTopup,
User,
Wallet,
)
from lnbits.core.services import update_wallet_balance
from lnbits.db import Filters, Page
from lnbits.decorators import check_admin, check_super_user, parse_filters
from lnbits.helpers import generate_filter_params_openapi
from lnbits.settings import EditableSettings, settings
users_router = APIRouter(prefix="/users/api/v1", dependencies=[Depends(check_admin)])
@users_router.get("/user")
@users_router.get(
"/user",
name="get accounts",
summary="Get paginated list of accounts",
openapi_extra=generate_filter_params_openapi(AccountFilters),
)
async def api_get_users(
filters: Filters = Depends(parse_filters(AccountFilters)),
) -> Page[Account]:

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,156 @@
function generateChart(canvas, rawData) {
const data = rawData.reduce(
(previous, current) => {
previous.labels.push(current.date)
previous.income.push(current.income)
previous.spending.push(current.spending)
previous.cumulative.push(current.balance)
return previous
},
{
labels: [],
income: [],
spending: [],
cumulative: []
}
)
return new Chart(canvas.getContext('2d'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
data: data.cumulative,
type: 'line',
label: 'balance',
backgroundColor: '#673ab7', // deep-purple
borderColor: '#673ab7',
borderWidth: 4,
pointRadius: 3,
fill: false
},
{
data: data.income,
type: 'bar',
label: 'in',
barPercentage: 0.75,
backgroundColor: window.Color('rgb(76,175,80)').alpha(0.5).rgbString() // green
},
{
data: data.spending,
type: 'bar',
label: 'out',
barPercentage: 0.75,
backgroundColor: window.Color('rgb(233,30,99)').alpha(0.5).rgbString() // pink
}
]
},
options: {
title: {
text: 'Chart.js Combo Time Scale'
},
tooltips: {
mode: 'index',
intersect: false
},
scales: {
xAxes: [
{
type: 'time',
display: true,
//offset: true,
time: {
minUnit: 'hour',
stepSize: 3
}
}
]
},
// performance tweaks
animation: {
duration: 0
},
elements: {
line: {
tension: 0
}
}
}
})
}
Vue.component('payment-chart', {
name: 'payment-chart',
props: ['wallet'],
data: function () {
return {
paymentsChart: {
show: false,
group: {
value: 'hour',
label: 'Hour'
},
groupOptions: [
{value: 'hour', label: 'Hour'},
{value: 'day', label: 'Day'},
{value: 'week', label: 'Week'},
{value: 'month', label: 'Month'},
{value: 'year', label: 'Year'}
],
instance: null
}
}
},
methods: {
showChart: function () {
this.paymentsChart.show = true
LNbits.api
.request(
'GET',
'/api/v1/payments/history?group=' + this.paymentsChart.group.value,
this.wallet.adminkey
)
.then(response => {
this.$nextTick(() => {
if (this.paymentsChart.instance) {
this.paymentsChart.instance.destroy()
}
this.paymentsChart.instance = generateChart(
this.$refs.canvas,
response.data
)
})
})
.catch(err => {
LNbits.utils.notifyApiError(err)
this.paymentsChart.show = false
})
}
},
template: `
<span id="payment-chart">
<q-btn dense flat round icon="show_chart" color="grey" @click="showChart" >
<q-tooltip>
<span v-text="$t('chart_tooltip')"></span>
</q-tooltip>
</q-btn>
<q-dialog v-model="paymentsChart.show" position="top">
<q-card class="q-pa-sm" style="width: 800px; max-width: unset">
<q-card-section>
<div class="row q-gutter-sm justify-between">
<div class="text-h6">Payments Chart</div>
<q-select label="Group" filled dense v-model="paymentsChart.group"
style="min-width: 120px"
:options="paymentsChart.groupOptions"
>
</q-select>
</div>
<canvas ref="canvas" width="600" height="400"></canvas>
</q-card-section>
</q-card>
</q-dialog>
</span>
`
})

View file

@ -0,0 +1,383 @@
Vue.component('payment-list', {
name: 'payment-list',
props: ['wallet', 'mobileSimple', 'lazy'],
data: function () {
return {
denomination: LNBITS_DENOMINATION,
payments: [],
paymentsTable: {
columns: [
{
name: 'time',
align: 'left',
label: this.$t('memo') + '/' + this.$t('date'),
field: 'date',
sortable: true
},
{
name: 'amount',
align: 'right',
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
field: 'sat',
sortable: true
}
],
pagination: {
rowsPerPage: 10,
page: 1,
sortBy: 'time',
descending: true,
rowsNumber: 10
},
search: null,
loading: false
},
paymentsCSV: {
columns: [
{
name: 'pending',
align: 'left',
label: 'Pending',
field: 'pending'
},
{
name: 'memo',
align: 'left',
label: this.$t('memo'),
field: 'memo'
},
{
name: 'time',
align: 'left',
label: this.$t('date'),
field: 'date',
sortable: true
},
{
name: 'amount',
align: 'right',
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
field: 'sat',
sortable: true
},
{
name: 'fee',
align: 'right',
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
field: 'fee'
},
{
name: 'tag',
align: 'right',
label: this.$t('tag'),
field: 'tag'
},
{
name: 'payment_hash',
align: 'right',
label: this.$t('payment_hash'),
field: 'payment_hash'
},
{
name: 'payment_proof',
align: 'right',
label: this.$t('payment_proof'),
field: 'payment_proof'
},
{
name: 'webhook',
align: 'right',
label: this.$t('webhook'),
field: 'webhook'
},
{
name: 'fiat_currency',
align: 'right',
label: 'Fiat Currency',
field: row => row.extra.wallet_fiat_currency
},
{
name: 'fiat_amount',
align: 'right',
label: 'Fiat Amount',
field: row => row.extra.wallet_fiat_amount
}
],
filter: null,
loading: false
}
}
},
computed: {
filteredPayments: function () {
var q = this.paymentsTable.search
if (!q || q === '') return this.payments
return LNbits.utils.search(this.payments, q)
},
paymentsOmitter() {
if (this.$q.screen.lt.md && this.mobileSimple) {
return this.payments.length > 0 ? [this.payments[0]] : []
}
return this.payments
},
pendingPaymentsExist: function () {
return this.payments.findIndex(payment => payment.pending) !== -1
}
},
methods: {
fetchPayments: function (props) {
const params = LNbits.utils.prepareFilterQuery(this.paymentsTable, props)
return LNbits.api
.getPayments(this.wallet, params)
.then(response => {
this.paymentsTable.loading = false
this.paymentsTable.pagination.rowsNumber = response.data.total
this.payments = response.data.data.map(obj => {
return LNbits.map.payment(obj)
})
})
.catch(err => {
this.paymentsTable.loading = false
LNbits.utils.notifyApiError(err)
})
},
paymentTableRowKey: function (row) {
return row.payment_hash + row.amount
},
exportCSV: function () {
// status is important for export but it is not in paymentsTable
// because it is manually added with payment detail link and icons
// and would cause duplication in the list
const pagination = this.paymentsTable.pagination
const query = {
sortby: pagination.sortBy ?? 'time',
direction: pagination.descending ? 'desc' : 'asc'
}
const params = new URLSearchParams(query)
LNbits.api.getPayments(this.wallet, params).then(response => {
const payments = response.data.data.map(LNbits.map.payment)
LNbits.utils.exportCSV(
this.paymentsCSV.columns,
payments,
this.wallet.name + '-payments'
)
})
}
},
watch: {
lazy: function (newVal) {
debugger
if (newVal === true) this.fetchPayments()
}
},
created: function () {
if (this.lazy === undefined) this.fetchPayments()
},
template: `
<q-card
:style="$q.screen.lt.md ? {
background: $q.screen.lt.md ? 'none !important': ''
, boxShadow: $q.screen.lt.md ? 'none !important': ''
, marginTop: $q.screen.lt.md ? '0px !important': ''
} : ''"
>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h5
class="text-subtitle1 q-my-none"
:v-text="$t('transactions')"
></h5>
</div>
<div class="gt-sm col-auto">
<q-btn flat color="grey" @click="exportCSV" :label="$t('export_csv')" ></q-btn>
<payment-chart :wallet="wallet" />
</div>
</div>
<q-input
:style="$q.screen.lt.md ? {
display: mobileSimple ? 'none !important': ''
} : ''"
filled
dense
clearable
v-model="paymentsTable.search"
debounce="300"
:placeholder="$t('search_by_tag_memo_amount')"
class="q-mb-md"
>
</q-input>
<q-table
dense
flat
:data="paymentsOmitter"
:row-key="paymentTableRowKey"
:columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"
:no-data-label="$t('no_transactions')"
:filter="paymentsTable.search"
:loading="paymentsTable.loading"
:hide-header="mobileSimple"
:hide-bottom="mobileSimple"
@request="fetchPayments"
>
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
v-text="col.label"
></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width class="text-center">
<q-icon
v-if="props.row.isPaid"
size="14px"
:name="props.row.isOut ? 'call_made' : 'call_received'"
:color="props.row.isOut ? 'pink' : 'green'"
@click="props.expand = !props.expand"
></q-icon>
<q-icon
v-else
name="settings_ethernet"
color="grey"
@click="props.expand = !props.expand"
>
<q-tooltip
><span v-text="$t('pending')"></span
></q-tooltip>
</q-icon>
</q-td>
<q-td
key="time"
:props="props"
style="white-space: normal; word-break: break-all"
>
<q-badge
v-if="props.row.tag"
color="yellow"
text-color="black"
>
<a
v-text="'#'+props.row.tag"
class="inherit"
:href="['/', props.row.tag].join('')"
></a>
</q-badge>
<span v-text="props.row.memo"></span>
<br />
<i>
<span v-text="props.row.dateFrom"></span>
<q-tooltip
><span v-text="props.row.date"></span
></q-tooltip>
</i>
</q-td>
<q-td
auto-width
key="amount"
v-if="denomination != 'sats'"
:props="props"
class="lol1"
v-text="parseFloat(String(props.row.fsat).replaceAll(',', '')) / 100"
>
</q-td>
<q-td class="lol2" auto-width key="amount" v-else :props="props">
<span v-text="props.row.fsat"></span>
<br />
<i v-if="props.row.extra.wallet_fiat_currency">
<span
v-text="LNbits.utils.formatCurrency(props.row.extra.wallet_fiat_currency, props.row.extra.wallet_fiat_amount)"
></span>
<br />
</i>
<i v-if="props.row.extra.fiat_currency">
<span
v-text="LNbits.utils.formatCurrency(props.row.extra.fiat_currency, props.row.extra.fiat_amount)"
></span>
</i>
</q-td>
<q-dialog v-model="props.expand" :props="props" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-lg">
<div v-if="props.row.isIn && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
<span v-text="$t('invoice_waiting')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
<div
v-if="props.row.bolt11"
class="text-center q-mb-lg"
>
<a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
<lnbits-qrcode
:value="'lightning:' + props.row.bolt11.toUpperCase()"
></lnbits-qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn
outline
color="grey"
@click="copyText(props.row.bolt11)"
:label="$t('copy_invoice')"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
:label="$t('close')"
></q-btn>
</div>
</div>
<div v-else-if="props.row.isPaid && props.row.isIn">
<q-icon
size="18px"
:name="'call_received'"
:color="'green'"
></q-icon>
<span v-text="$t('payment_received')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isPaid && props.row.isOut">
<q-icon
size="18px"
:name="'call_made'"
:color="'pink'"
></q-icon>
<span v-text="$t('payment_sent')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isOut && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
<span v-text="$t('outgoing_payment_pending')"></span>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
</div>
</q-card>
</q-dialog>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-card>
`
})

View file

@ -4,10 +4,14 @@ new Vue({
data: function () {
return {
isSuperUser: false,
activeWallet: {},
wallet: {},
cancel: {},
users: [],
wallets: [],
paymentDialog: {
show: false
},
walletDialog: {
show: false
},
@ -324,6 +328,10 @@ new Vue({
LNbits.utils.notifyApiError(error)
})
},
showPayments(wallet_id) {
this.activeWallet = this.wallets.find(wallet => wallet.id === wallet_id)
this.paymentDialog.show = true
},
toggleAdmin(user_id) {
LNbits.api
.request('GET', `/users/api/v1/user/${user_id}/admin`)

View file

@ -1,90 +1,8 @@
/* globals windowMixin, decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, Chart, decryptLnurlPayAES */
/* globals windowMixin, decode, Vue, VueQrcodeReader, VueQrcode, Quasar, LNbits, _, EventHub, decryptLnurlPayAES */
Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader)
function generateChart(canvas, rawData) {
const data = rawData.reduce(
(previous, current) => {
previous.labels.push(current.date)
previous.income.push(current.income)
previous.spending.push(current.spending)
previous.cumulative.push(current.balance)
return previous
},
{
labels: [],
income: [],
spending: [],
cumulative: []
}
)
return new Chart(canvas.getContext('2d'), {
type: 'bar',
data: {
labels: data.labels,
datasets: [
{
data: data.cumulative,
type: 'line',
label: 'balance',
backgroundColor: '#673ab7', // deep-purple
borderColor: '#673ab7',
borderWidth: 4,
pointRadius: 3,
fill: false
},
{
data: data.income,
type: 'bar',
label: 'in',
barPercentage: 0.75,
backgroundColor: window.Color('rgb(76,175,80)').alpha(0.5).rgbString() // green
},
{
data: data.spending,
type: 'bar',
label: 'out',
barPercentage: 0.75,
backgroundColor: window.Color('rgb(233,30,99)').alpha(0.5).rgbString() // pink
}
]
},
options: {
title: {
text: 'Chart.js Combo Time Scale'
},
tooltips: {
mode: 'index',
intersect: false
},
scales: {
xAxes: [
{
type: 'time',
display: true,
//offset: true,
time: {
minUnit: 'hour',
stepSize: 3
}
}
]
},
// performance tweaks
animation: {
duration: 0
},
elements: {
line: {
tension: 0
}
}
}
})
}
new Vue({
el: '#vue',
mixins: [windowMixin],
@ -126,118 +44,6 @@ new Vue({
camera: 'auto'
}
},
payments: [],
paymentsTable: {
columns: [
{
name: 'time',
align: 'left',
label: this.$t('memo') + '/' + this.$t('date'),
field: 'date',
sortable: true
},
{
name: 'amount',
align: 'right',
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
field: 'sat',
sortable: true
}
],
pagination: {
rowsPerPage: 10,
page: 1,
sortBy: 'time',
descending: true,
rowsNumber: 10
},
search: null,
loading: false
},
paymentsCSV: {
columns: [
{
name: 'pending',
align: 'left',
label: 'Pending',
field: 'pending'
},
{
name: 'memo',
align: 'left',
label: this.$t('memo'),
field: 'memo'
},
{
name: 'time',
align: 'left',
label: this.$t('date'),
field: 'date',
sortable: true
},
{
name: 'amount',
align: 'right',
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
field: 'sat',
sortable: true
},
{
name: 'fee',
align: 'right',
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
field: 'fee'
},
{
name: 'tag',
align: 'right',
label: this.$t('tag'),
field: 'tag'
},
{
name: 'payment_hash',
align: 'right',
label: this.$t('payment_hash'),
field: 'payment_hash'
},
{
name: 'payment_proof',
align: 'right',
label: this.$t('payment_proof'),
field: 'payment_proof'
},
{
name: 'webhook',
align: 'right',
label: this.$t('webhook'),
field: 'webhook'
},
{
name: 'fiat_currency',
align: 'right',
label: 'Fiat Currency',
field: row => row.extra.wallet_fiat_currency
},
{
name: 'fiat_amount',
align: 'right',
label: 'Fiat Amount',
field: row => row.extra.wallet_fiat_amount
}
],
filter: null,
loading: false
},
paymentsChart: {
show: false,
group: {value: 'hour', label: 'Hour'},
groupOptions: [
{value: 'month', label: 'Month'},
{value: 'day', label: 'Day'},
{value: 'hour', label: 'Hour'}
],
instance: null
},
disclaimerDialog: {
show: false,
location: window.location
@ -268,63 +74,21 @@ new Vue({
)
}
},
filteredPayments: function () {
var q = this.paymentsTable.search
if (!q || q === '') return this.payments
return LNbits.utils.search(this.payments, q)
},
paymentsOmitter() {
if (this.$q.screen.lt.md && this.mobileSimple) {
return this.payments.length > 0 ? [this.payments[0]] : []
}
return this.payments
},
canPay: function () {
if (!this.parse.invoice) return false
return this.parse.invoice.sat <= this.balance
},
pendingPaymentsExist: function () {
return this.payments.findIndex(payment => payment.pending) !== -1
}
},
methods: {
msatoshiFormat: function (value) {
return LNbits.utils.formatSat(value / 1000)
},
paymentTableRowKey: function (row) {
return row.payment_hash + row.amount
},
closeCamera: function () {
this.parse.camera.show = false
},
showCamera: function () {
this.parse.camera.show = true
},
showChart: function () {
this.paymentsChart.show = true
LNbits.api
.request(
'GET',
'/api/v1/payments/history?group=' + this.paymentsChart.group.value,
this.g.wallet.adminkey
)
.then(response => {
this.$nextTick(() => {
if (this.paymentsChart.instance) {
this.paymentsChart.instance.destroy()
}
this.paymentsChart.instance = generateChart(
this.$refs.canvas,
response.data
)
})
})
.catch(err => {
LNbits.utils.notifyApiError(err)
this.paymentsChart.show = false
})
},
focusInput(el) {
this.$nextTick(() => this.$refs[el].focus())
},
@ -359,8 +123,6 @@ new Vue({
}, 10000)
},
onPaymentReceived: function (paymentHash) {
this.fetchPayments()
if (this.receive.paymentHash === paymentHash) {
this.receive.show = false
this.receive.paymentHash = null
@ -407,8 +169,6 @@ new Vue({
})
}
}
this.fetchPayments()
})
.catch(err => {
LNbits.utils.notifyApiError(err)
@ -587,7 +347,6 @@ new Vue({
this.parse.show = false
clearInterval(this.parse.paymentChecker)
dismissPaymentMsg()
this.fetchPayments()
}
})
}, 2000)
@ -627,7 +386,6 @@ new Vue({
if (res.data.paid) {
dismissPaymentMsg()
clearInterval(this.parse.paymentChecker)
this.fetchPayments()
// show lnurlpay success action
if (response.data.success_action) {
switch (response.data.success_action.tag) {
@ -740,27 +498,10 @@ new Vue({
})
})
.catch(err => {
this.paymentsTable.loading = false
LNbits.utils.notifyApiError(err)
})
})
},
fetchPayments: function (props) {
const params = LNbits.utils.prepareFilterQuery(this.paymentsTable, props)
return LNbits.api
.getPayments(this.g.wallet, params)
.then(response => {
this.paymentsTable.loading = false
this.paymentsTable.pagination.rowsNumber = response.data.total
this.payments = response.data.data.map(obj => {
return LNbits.map.payment(obj)
})
})
.catch(err => {
this.paymentsTable.loading = false
LNbits.utils.notifyApiError(err)
})
},
fetchBalance: function () {
LNbits.api.getWallet(this.g.wallet).then(response => {
this.balance = Math.floor(response.data.balance / 1000)
@ -785,31 +526,9 @@ new Vue({
})
.catch(e => console.error(e))
},
formatFiat(currency, amount) {
return LNbits.utils.formatCurrency(amount, currency)
},
updateBalanceCallback: function (res) {
this.balance += res.value
},
exportCSV: function () {
// status is important for export but it is not in paymentsTable
// because it is manually added with payment detail link and icons
// and would cause duplication in the list
const pagination = this.paymentsTable.pagination
const query = {
sortby: pagination.sortBy ?? 'time',
direction: pagination.descending ? 'desc' : 'asc'
}
const params = new URLSearchParams(query)
LNbits.api.getPayments(this.g.wallet, params).then(response => {
const payments = response.data.data.map(LNbits.map.payment)
LNbits.utils.exportCSV(
this.paymentsCSV.columns,
payments,
this.g.wallet.name + '-payments'
)
})
},
pasteToTextArea: function () {
this.$refs.textArea.focus() // Set cursor to textarea
navigator.clipboard.readText().then(text => {
@ -817,14 +536,6 @@ new Vue({
})
}
},
watch: {
payments: function () {
this.fetchBalance()
},
'paymentsChart.group': function () {
this.showChart()
}
},
created: function () {
let urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('lightning') || urlParams.has('lnurl')) {
@ -836,8 +547,6 @@ new Vue({
if (this.$q.screen.lt.md) {
this.mobileSimple = true
}
this.fetchPayments()
this.update.name = this.g.wallet.name
this.update.currency = this.g.wallet.currency
this.receive.units = ['sat', ...window.currencies]

View file

@ -37,6 +37,8 @@
"js/components.js",
"js/components/lnbits-funding-sources.js",
"js/components/extension-settings.js",
"js/components/payment-list.js",
"js/components/payment-chart.js",
"js/event-reactions.js",
"js/bolt11-decoder.js"
],

View file

@ -86,6 +86,8 @@
"js/components.js",
"js/components/lnbits-funding-sources.js",
"js/components/extension-settings.js",
"js/components/payment-list.js",
"js/components/payment-chart.js",
"js/event-reactions.js",
"js/bolt11-decoder.js"
],