mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-26 23:51:55 +01:00
648 lines
18 KiB
HTML
648 lines
18 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="primary" @click="formDialog.show = true"
|
|
>New wallet
|
|
</q-btn>
|
|
<q-btn unelevated color="primary" 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
|
|
>
|
|
{{ col.label }}
|
|
</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="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="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
|
|
>
|
|
{{ col.value }}
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
|
|
<q-card>
|
|
<div class="row justify-center q-gutter-x-md items-center">
|
|
<div class="text-h3 q-pa-sm">{{satBtc(utxos.total)}}</div>
|
|
<q-btn flat @click="utxos.sats = !utxos.sats">
|
|
{{utxos.sats ? ' sats' : ' BTC'}}</q-btn
|
|
>
|
|
</div>
|
|
</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">Transactions</h5>
|
|
</div>
|
|
<div class="col-auto">
|
|
<q-input
|
|
borderless
|
|
dense
|
|
debounce="300"
|
|
v-model="TxosTable.filter"
|
|
placeholder="Search"
|
|
>
|
|
<template v-slot:append>
|
|
<q-icon name="search"></q-icon>
|
|
</template>
|
|
</q-input>
|
|
</div>
|
|
</div>
|
|
<q-table
|
|
flat
|
|
dense
|
|
:data="utxos.data"
|
|
row-key="txid"
|
|
:columns="TxosTable.columns"
|
|
:pagination.sync="TxosTable.pagination"
|
|
:filter="TxosTable.filter"
|
|
>
|
|
<template v-slot:header="props">
|
|
<q-tr :props="props">
|
|
<q-th
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
:props="props"
|
|
auto-width
|
|
>
|
|
{{ col.label }}
|
|
</q-th>
|
|
<q-th auto-width></q-th>
|
|
</q-tr>
|
|
</template>
|
|
<template v-slot:body="props">
|
|
<q-tr :props="props">
|
|
<q-td
|
|
v-for="col in props.cols"
|
|
:key="col.name"
|
|
:props="props"
|
|
auto-width
|
|
:class="col.value == true ? 'text-green-13 text-weight-bold' : ''"
|
|
>
|
|
{{ col.name == 'value' ? satBtc(col.value) : col.value }}
|
|
</q-td>
|
|
</q-tr>
|
|
</template>
|
|
</q-table>
|
|
</q-card-section>
|
|
</q-card>
|
|
</div>
|
|
|
|
{% endraw %}
|
|
|
|
<div class="col-12 col-md-5 q-gutter-y-md">
|
|
<q-card>
|
|
<q-card-section>
|
|
<h6 class="text-subtitle1 q-my-none">
|
|
{{SITE_TITLE}} Watch Only Extension
|
|
</h6>
|
|
</q-card-section>
|
|
<q-card-section class="q-pa-none">
|
|
<q-separator></q-separator>
|
|
<q-list> {% include "watchonly/_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="Account Extended Public Key; xpub, ypub, zpub; Bitcoin Descriptor"
|
|
></q-input>
|
|
|
|
<div class="row q-mt-lg">
|
|
<q-btn
|
|
unelevated
|
|
color="primary"
|
|
: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="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 type="text/javascript" src="https://mempool.space/mempool.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 mapAddresses = 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: [],
|
|
AddressesLinks: [],
|
|
currentaddress: '',
|
|
Addresses: {
|
|
show: false,
|
|
data: null
|
|
},
|
|
utxos: {
|
|
data: [],
|
|
total: 0,
|
|
sats: true
|
|
},
|
|
mempool: {
|
|
endpoint: ''
|
|
},
|
|
WalletsTable: {
|
|
columns: [
|
|
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
|
{
|
|
name: 'title',
|
|
align: 'left',
|
|
label: 'Title',
|
|
field: 'title'
|
|
},
|
|
{
|
|
name: 'masterpub',
|
|
align: 'left',
|
|
label: 'MasterPub',
|
|
field: 'masterpub'
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
}
|
|
},
|
|
TxosTable: {
|
|
columns: [
|
|
{
|
|
name: 'value',
|
|
align: 'left',
|
|
label: 'Value',
|
|
field: 'value',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'confirmed',
|
|
align: 'center',
|
|
label: 'Confirmed',
|
|
field: 'confirmed',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'date',
|
|
align: 'left',
|
|
label: 'Date',
|
|
field: 'date',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'txid',
|
|
align: 'left',
|
|
label: 'ID',
|
|
field: 'txid',
|
|
sortable: true
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10
|
|
},
|
|
uxtosFilter: ''
|
|
},
|
|
|
|
formDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
qrCodeDialog: {
|
|
show: false,
|
|
data: null
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
getAddressDetails: function (address) {
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/watchonly/api/v1/mempool/' + address,
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
return reponse.data
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
getAddresses: function (walletID) {
|
|
var self = this
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/watchonly/api/v1/addresses/' + walletID,
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
self.Addresses.data = response.data
|
|
self.currentaddress =
|
|
self.Addresses.data[self.Addresses.data.length - 1].address
|
|
self.AddressesLinks = response.data.map(function (obj) {
|
|
return mapAddresses(obj)
|
|
})
|
|
self.fetchUtxos()
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
getFreshAddress: function (walletID) {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/watchonly/api/v1/address/' + walletID,
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
self.Addresses.data = response.data
|
|
self.currentaddress =
|
|
self.Addresses.data[self.Addresses.data.length - 1].address
|
|
})
|
|
},
|
|
getMempool: function () {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/watchonly/api/v1/mempool',
|
|
this.g.user.wallets[0].adminkey
|
|
)
|
|
.then(function (response) {
|
|
self.mempool.endpoint = response.data.endpoint
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
|
|
updateMempool: function () {
|
|
var self = this
|
|
var wallet = this.g.user.wallets[0]
|
|
LNbits.api
|
|
.request(
|
|
'PUT',
|
|
'/watchonly/api/v1/mempool',
|
|
wallet.adminkey,
|
|
self.mempool
|
|
)
|
|
.then(function (response) {
|
|
self.mempool.endpoint = response.data.endpoint
|
|
self.walletLinks.push(mapwalletLink(response.data))
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
fetchUtxos: async function () {
|
|
const {
|
|
bitcoin: {addresses}
|
|
} = mempoolJS()
|
|
const address = this.AddressesLinks.map(x => x.address)
|
|
const fetchUtxo = async () => {
|
|
let txs = []
|
|
// const address = [
|
|
// '3Er3w82WqPLL4ew23taUZcFQwbZq6PE9TK',
|
|
// '16CwtWRwQYLojaVZZFCgnaM6SQuNefqwrc',
|
|
// '1KZB6FqnnMWySk75uvFKuPzHct1tMXHSSR'
|
|
// ] //test addresses
|
|
for (add of address) {
|
|
let addressTxsUtxo = await addresses.getAddressTxsUtxo({
|
|
address: add
|
|
})
|
|
txs = [...txs, ...addressTxsUtxo]
|
|
}
|
|
return txs
|
|
}
|
|
let utxos = await fetchUtxo()
|
|
utxos = utxos
|
|
.reduce((ac, x) => {
|
|
if (!ac)
|
|
return [
|
|
{
|
|
txid: x.txid,
|
|
confirmed: x.status.confirmed,
|
|
value: x.value,
|
|
date: moment(x.status?.block_time * 1000).format('LLL'),
|
|
sort: x.status?.block_time
|
|
}
|
|
]
|
|
if (!ac.some(y => y.txid == x.txid))
|
|
return [
|
|
...ac,
|
|
{
|
|
txid: x.txid,
|
|
confirmed: x.status.confirmed,
|
|
value: x.value,
|
|
date: moment(x.status?.block_time * 1000).format('LLL'),
|
|
sort: x.status?.block_time
|
|
}
|
|
]
|
|
return
|
|
}, [])
|
|
.sort((a, b) => b.sort - a.sort)
|
|
let total = utxos.reduce((total, y) => {
|
|
total += y?.value || 0
|
|
return total
|
|
}, 0)
|
|
this.utxos.data = utxos
|
|
this.utxos.total = total
|
|
},
|
|
getWalletLinks: function () {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request(
|
|
'GET',
|
|
'/watchonly/api/v1/wallet',
|
|
this.g.user.wallets[0].inkey
|
|
)
|
|
.then(function (response) {
|
|
self.walletLinks = response.data.map(function (obj) {
|
|
self.getAddresses(obj.id)
|
|
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
|
|
this.getAddresses(linkId)
|
|
self.current = linkId
|
|
self.Addresses.show = true
|
|
},
|
|
sendFormData: function () {
|
|
var wallet = this.g.user.wallets[0]
|
|
var data = _.omit(this.formDialog.data, 'wallet')
|
|
this.createWalletLink(wallet, data)
|
|
},
|
|
createWalletLink: function (wallet, data) {
|
|
var self = this
|
|
|
|
LNbits.api
|
|
.request('POST', '/watchonly/api/v1/wallet', wallet.adminkey, data)
|
|
.then(function (response) {
|
|
self.walletLinks.push(mapWalletLink(response.data))
|
|
self.formDialog.show = false
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
deleteWalletLink: function (linkId) {
|
|
var self = this
|
|
var link = _.findWhere(this.walletLinks, {id: linkId})
|
|
LNbits.utils
|
|
.confirmDialog('Are you sure you want to delete this pay link?')
|
|
.onOk(function () {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/watchonly/api/v1/wallet/' + linkId,
|
|
self.g.user.wallets[0].adminkey
|
|
)
|
|
.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)
|
|
},
|
|
satBtc(val) {
|
|
return this.utxos.sats
|
|
? LNbits.utils.formatSat(val)
|
|
: val == 0
|
|
? 0.0
|
|
: (val / 100000000).toFixed(8)
|
|
}
|
|
},
|
|
created: function () {
|
|
if (this.g.user.wallets.length) {
|
|
var getMempool = this.getMempool
|
|
getMempool()
|
|
var getWalletLinks = this.getWalletLinks
|
|
getWalletLinks()
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
{% endblock %}
|