lnbits-legend/lnbits/extensions/satspay/templates/watchonly/index.html
benarc ecfcc167f0 Splitting into x2 extensions
Splitting extension into 2, SatsPay Server, a BTCPay Server type extension, and WatchOnly
2021-02-21 17:53:43 +00:00

847 lines
23 KiB
HTML

{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-7 q-gutter-y-md">
<q-card>
<q-card-section>
{% raw %}
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
>New wallet </q-btn
>
<q-btn unelevated color="deep-purple"
icon="edit">
<div class="cursor-pointer">
<q-tooltip>
Point to another Mempool
</q-tooltip>
{{ this.mempool.endpoint }}
<q-popup-edit v-model="mempool.endpoint">
<q-input color="accent" v-model="mempool.endpoint">
</q-input>
<center><q-btn
flat
dense
@click="updateMempool()"
v-close-popup
>set</q-btn>
<q-btn
flat
dense
v-close-popup
>cancel</q-btn>
</center>
</q-popup-edit>
</div>
</q-btn
>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Wallets</h5>
</div>
<div class="col-auto">
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search"></q-icon>
</template>
</q-input>
</div>
</div>
<q-table
flat
dense
:data="walletLinks"
row-key="id"
:columns="WalletsTable.columns"
:pagination.sync="WalletsTable.pagination"
:filter="filter"
>
<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" auto-width>
<div v-if="col.name == 'id'"></div>
<div v-else>
{{ col.label }}
</div>
</q-th>
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn
unelevated
dense
size="xs"
icon="toll"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="formDialogCharge.show = true, formDialogCharge.data.walletid = props.row.id"
>
<q-tooltip>
Charge link
</q-tooltip>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="dns"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
>
<q-tooltip>
Adresses
</q-tooltip>
</q-btn>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteWalletLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
<div v-if="col.name == 'id'"></div>
<div v-else>
{{ col.value }}
</div>
</q-td>
</q-tr>
</template>
</q-table>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Paylinks</h5>
</div>
<div class="col-auto">
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append>
<q-icon name="search"></q-icon>
</template>
</q-input>
</div>
</div>
<q-table
flat
dense
:data="ChargeLinks"
row-key="id"
:columns="ChargesTable.columns"
:pagination.sync="ChargesTable.pagination"
:filter="filter"
>
<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" auto-width>
<div v-if="col.name == 'id'"></div>
<div v-else>
{{ col.label }}
</div>
</q-th>
<q-th auto-width></q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-icon v-if="props.row.timeleft < 1 && props.row.amount_paid < props.row.amount"
#unelevated
dense
size="xs"
name="error"
:color="($q.dark.isActive) ? 'red' : 'red'"
></q-icon>
<q-icon v-else-if="props.row.amount_paid > props.row.amount"
#unelevated
dense
size="xs"
name="check"
:color="($q.dark.isActive) ? 'green' : 'green'"
></q-icon>
<q-icon v-else="props.row.amount_paid < props.row.amount && props.row.timeleft > 1"
#unelevated
dense
size="xs"
name="cached"
:color="($q.dark.isActive) ? 'blue' : 'blue'"
></q-icon>
<q-btn
flat
dense
size="xs"
@click="openUpdateDialog(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteWalletLink(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
<div v-if="col.name == 'id'"></div>
<div v-else>
{{ col.value }}
</div>
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
</div>
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
LNbits satspay Extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list>
{% include "satspay/_api_docs.html" %}
</q-list>
</q-card-section>
</q-card>
</div>
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialog.data.title"
type="text"
label="Title"
></q-input>
<q-input
filled
type="textarea"
v-model="formDialog.data.masterpub"
height="50px"
autogrow
label="Master Public Key, either xpub, ypub, zpub"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialog.data.id"
unelevated
color="deep-purple"
type="submit"
>Update Watch-only Wallet</q-btn
>
<q-btn
v-else
unelevated
color="deep-purple"
:disable="
formDialog.data.masterpub == null ||
formDialog.data.title == null"
type="submit"
>Create Watch-only Wallet</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="formDialogCharge.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormDataCharge" class="q-gutter-md">
<q-input
filled
dense
v-model.trim="formDialogCharge.data.title"
type="text"
label="Title"
></q-input>
<q-input
filled
dense
v-model.trim="formDialogCharge.data.amount"
type="number"
label="Amount (sats)"
></q-input>
<q-input
filled
dense
v-model.trim="formDialogCharge.data.time"
type="number"
label="Time (secs)"
> </q-input>
<div class="row q-mt-lg">
<q-btn
v-if="formDialogCharge.data.id"
unelevated
color="deep-purple"
type="submit"
>Update Paylink</q-btn
>
<q-btn
v-else
unelevated
color="deep-purple"
:disable="
formDialogCharge.data.time == null ||
formDialogCharge.data.amount == null"
type="submit"
>Create Paylink</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="Addresses.show" position="top">
<q-card v-if="Addresses.data" class="q-pa-lg lnbits__dialog-card">
{% raw %}
<h5 class="text-subtitle1 q-my-none">Addresses</h5>
<q-separator></q-separator><br/>
<p><strong>Current:</strong>
{{ currentaddress }}
<q-btn
flat
dense
size="ms"
icon="visibility"
type="a"
:href="mempool.endpoint + '/address/' + currentaddress"
target="_blank"
></q-btn>
</p>
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="currentaddress"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<p style="word-break: break-all;">
<q-scroll-area style="height: 200px; max-width: 100%;">
<q-list bordered v-for="data in Addresses.data.slice().reverse()">
<q-item>
<q-item-section>{{ data.address }}</q-item-section>
<q-btn
flat
dense
size="ms"
icon="visibility"
type="a"
:href="mempool.endpoint + '/address/' + data.address"
target="_blank"
></q-btn>
</q-item>
</q-list>
</q-scroll-area>
</p>
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="getFreshAddress(current)"
class="q-ml-sm"
>Get fresh address</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
{% endraw %}
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<style>
</style>
<script>
Vue.component(VueQrcode.name, VueQrcode)
Vue.filter('reverse', function(value) {
// slice to make a copy of array, then reverse the copy
return value.slice().reverse();
});
var locationPath = [
window.location.protocol,
'//',
window.location.hostname,
window.location.pathname
].join('')
var mapWalletLink = function (obj) {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
return obj
}
var mapCharge = function (obj) {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
return obj
}
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
filter: '',
balance: null,
checker: null,
walletLinks: [],
ChargeLinks: [],
currentaddress: "",
Addresses: {
show: false,
data: null
},
mempool:{
endpoint:""
},
WalletsTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{
name: 'title',
align: 'left',
label: 'Title',
field: 'title'
},
{
name: 'amount',
align: 'left',
label: 'Amount',
field: 'amount'
},
{
name: 'masterpub',
align: 'left',
label: 'MasterPub',
field: 'masterpub'
},
],
pagination: {
rowsPerPage: 10
}
},
ChargesTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{
name: 'title',
align: 'left',
label: 'Title',
field: 'title'
},
{
name: 'amount',
align: 'left',
label: 'Amount to pay',
field: 'amount'
},
{
name: 'balance',
align: 'left',
label: 'Balance',
field: 'amount_paid'
},
{
name: 'address',
align: 'left',
label: 'Address',
field: 'address'
},
{
name: 'time to pay',
align: 'left',
label: 'Time to Pay',
field: 'time_to_pay'
},
{
name: 'timeleft',
align: 'left',
label: 'Time left',
field: 'timeleft'
},
],
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
data: {}
},
formDialogCharge: {
show: false,
data: {}
},
qrCodeDialog: {
show: false,
data: null
}
}
},
methods: {
chargeRedirect: function (address){
window.location.href = this.mempool.endpoint + "/address/" + address;
},
getMempool: function () {
var self = this
LNbits.api
.request(
'GET',
'/satspay/api/v1/mempool',
this.g.user.wallets[0].inkey
)
.then(function (response) {
console.log(response.data.endpoint)
self.mempool.endpoint = response.data.endpoint
console.log(this.mempool.endpoint)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
updateMempool: function () {
var self = this
var wallet = this.g.user.wallets[0]
LNbits.api
.request(
'PUT',
'/satspay/api/v1/mempool',
wallet.inkey, self.mempool)
.then(function (response) {
self.mempool.endpoint = response.data.endpoint
self.walletLinks.push(mapwalletLink(response.data))
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
getWalletLinks: function () {
var self = this
LNbits.api
.request(
'GET',
'/satspay/api/v1/wallet',
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.walletLinks = response.data.map(function (obj) {
return mapWalletLink(obj)
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
closeFormDialog: function () {
this.formDialog.data = {
is_unique: false
}
},
openQrCodeDialog: function (linkId) {
var self = this
var getAddresses = this.getAddresses
getAddresses(linkId)
self.current = linkId
self.Addresses.show = true
},
openUpdateDialog: function (linkId) {
var link = _.findWhere(this.walletLinks, {id: linkId})
this.formDialog.data = _.clone(link._data)
this.formDialog.show = true
},
sendFormData: function () {
var wallet = this.g.user.wallets[0]
var data = _.omit(this.formDialog.data, 'wallet')
if (data.id) {
this.updateWalletLink(wallet, data)
} else {
this.createWalletLink(wallet, data)
}
},
getCharges: function () {
var self = this
var getAddressBalance = this.getAddressBalance
LNbits.api
.request(
'GET',
'/satspay/api/v1/ChargeLinks',
this.g.user.wallets[0].inkey
)
.then(function (response) {
var i
var now = parseInt(new Date() / 1000)
for (i = 0; i < response.data.length; i++) {
timeleft = response.data[i].time_to_pay - (now - response.data[i].time)
if (timeleft < 1) {
response.data[i].timeleft = 0
}
else{
response.data[i].timeleft = timeleft
}
}
self.ChargeLinks = response.data.map(function (obj) {
return mapCharge(obj)
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
sendFormDataCharge: function () {
var self = this
var wallet = self.g.user.wallets[0]
var data = self.formDialogCharge.data
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
if (data.id) {
this.updateCharge(wallet, data)
} else {
this.createCharge(wallet, data)
}
},
updateCharge: function (wallet, data) {
var self = this
LNbits.api
.request(
'PUT',
'/satspay/api/v1/Charge/' + data.id,
wallet.inkey, data)
.then(function (response) {
self.Charge = _.reject(self.Charge, function (obj) {
return obj.id === data.id
})
self.Charge.push(mapCharge(response.data))
self.formDialogPayLink.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
createCharge: function (wallet, data) {
var self = this
LNbits.api
.request('POST', '/satspay/api/v1/Charge', wallet.inkey, data)
.then(function (response) {
self.ChargeLinks.push(mapCharge(response.data))
self.formDialogCharge.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteCharge: function (linkId) {
var self = this
var link = _.findWhere(this.Charge, {id: linkId})
console.log(self.g.user.wallets[0].adminkey)
LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/satspay/api/v1/Charge/' + linkId,
self.g.user.wallets[0].inkey
)
.then(function (response) {
self.Charge = _.reject(self.Charge, function (obj) {
return obj.id === linkId
})})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
updateWalletLink: function (wallet, data) {
var self = this
LNbits.api
.request(
'PUT',
'/satspay/api/v1/wallet/' + data.id,
wallet.inkey, data)
.then(function (response) {
self.walletLinks = _.reject(self.walletLinks, function (obj) {
return obj.id === data.id
})
self.walletLinks.push(mapWalletLink(response.data))
self.formDialog.show = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
createWalletLink: function (wallet, data) {
var self = this
LNbits.api
.request('POST', '/satspay/api/v1/wallet', wallet.inkey, data)
.then(function (response) {
self.walletLinks.push(mapWalletLink(response.data))
self.formDialog.show = false
console.log(response.data[1][1])
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteWalletLink: function (linkId) {
var self = this
var link = _.findWhere(this.walletLinks, {id: linkId})
console.log(self.g.user.wallets[0].adminkey)
LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?')
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/satspay/api/v1/wallet/' + linkId,
self.g.user.wallets[0].inkey
)
.then(function (response) {
self.walletLinks = _.reject(self.walletLinks, function (obj) {
return obj.id === linkId
})})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
})
},
exportCSV: function () {
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
},
},
created: function () {
if (this.g.user.wallets.length) {
var getWalletLinks = this.getWalletLinks
getWalletLinks()
var getCharges = this.getCharges
getCharges()
var getMempool = this.getMempool
getMempool()
}
}
})
</script>
{% endblock %}