mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 15:10:41 +01:00
no more superuser url! delete cookie on logout add usr login feature fix node management * Cleaned up login form * CreateUser * information leak * cleaner parsing usr from url * rename decorators * login secret * fix: add back `superuser` command * chore: remove `fastapi_login` * fix: extract `token` from cookie * chore: prepare to extract user * feat: check user * chore: code clean-up * feat: happy flow working * fix: usr only login * fix: user already logged in * feat: check user in URL * fix: verify password at DB level * fix: do not show `Login` controls if user already logged in * fix: separate login endpoints * fix: remove `usr` param * chore: update error message * refactor: register method * feat: logout * chore: move comments * fix: remove user auth check from API * fix: user check unnecessary * fix: redirect after logout * chore: remove garbage files * refactor: simplify constructor call * fix: hide user icon if not authorized * refactor: rename auth env vars * chore: code clean-up * fix: add types for `python-jose` * fix: add types for `passlib` * fix: return type * feat: set default value for `auth_secret_key` to hash of super user * fix: default value * feat: rework login page * feat: ui polishing * feat: google auth * feat: add google auth * chore: remove `authlib` dependency * refactor: extract `_handle_sso_login` method * refactor: convert methods to `properties` * refactor: rename: `user_api` to `auth_api` * feat: store user info from SSO * chore: re-arange the buttons * feat: conditional rendering of login options * feat: correctly render buttons * fix: re-add `Claim Bitcoin` from the main page * fix: create wallet must send new user * fix: no `username-password` auth method * refactor: rename auth method * fix: do not force API level UUID4 validation * feat: add validation for username * feat: add account page * feat: update account * feat: add `has_password` for user * fix: email not editable * feat: validate email for existing account * fix: register check * feat: reset password * chore: code clean-up * feat: handle token expired * fix: only redirect if `text/html` * refactor: remove `OAuth2PasswordRequestForm` * chore: remove `python-multipart` dependency * fix: handle no headers for exception * feat: add back button on error screen * feat: show user profile image * fix: check account creation permissions * fix: auth for internal api call * chore: add some docs * chore: code clean-up * fix: rebase stuff * fix: default value types * refactor: customize error messages * fix: move types libs to dev dependencies * doc: specify the `Authorization callback URL` * fix: pass missing superuser id in node ui test * fix: keep usr param on wallet redirect removing usr param causes an issue if the browser doesnt yet have an access token. * fix: do not redirect if `wal` query param not present * fix: add nativeBuildInputs and buildInputs overrides to flake.nix * bump fastapi-sso to 0.9.0 which fixes some security issues * refactor: move the `lnbits_admin_extensions` to decorators * chore: bring package config from `dev` * chore: re-add dependencies * chore: re-add cev dependencies * chore: re-add mypy ignores * feat: i18n * refactor: move admin ext check to decorator (fix after rebase) * fix: label mapping * fix: re-fetch user after first wallet was created * fix: unlikely case that `user` is not found * refactor translations (move '*' to code) * reorganize deps in pyproject.toml, add comment * update flake.lock and simplify flake.nix after upstreaming overrides for fastapi-sso, types-passlib, types-pyasn1, types-python-jose were upstreamed in https://github.com/nix-community/poetry2nix/pull/1463 * fix: more relaxed email verification (by @prusnak) * fix: remove `\b` (boundaries) since we re using `fullmatch` * chore: `make bundle` --------- Co-authored-by: dni ⚡ <office@dnilabs.com> Co-authored-by: Arc <ben@arc.wales> Co-authored-by: jackstar12 <jkranawetter05@gmail.com> Co-authored-by: Pavol Rusnak <pavol@rusnak.io>
376 lines
10 KiB
HTML
376 lines
10 KiB
HTML
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
%} {% block page %}
|
|
|
|
<q-dialog v-model="nodeInfoDialog.show">
|
|
<lnbits-node-qrcode :info="nodeInfoDialog.data"></lnbits-node-qrcode>
|
|
</q-dialog>
|
|
|
|
<div class="row q-col-gutter-md justify-center">
|
|
<div class="col q-gutter-y-md">
|
|
<q-card>
|
|
<div class="q-pa-md">
|
|
<div class="q-gutter-y-md">
|
|
<q-tabs v-model="tab" active-color="primary" align="justify">
|
|
<q-tab
|
|
name="dashboard"
|
|
:label="$t('dashboard')"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
<q-tab
|
|
name="channels"
|
|
:label="$t('channels')"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
<q-tab
|
|
name="transactions"
|
|
:label="$t('transactions')"
|
|
@update="val => tab = val.name"
|
|
></q-tab>
|
|
</q-tabs>
|
|
</div>
|
|
</div>
|
|
|
|
<q-form name="settings_form" id="settings_form">
|
|
<q-tab-panels v-model="tab" animated>
|
|
{% include "node/_tab_dashboard.html" %} {% include
|
|
"node/_tab_channels.html" %} {% include "node/_tab_transactions.html"
|
|
%}
|
|
</q-tab-panels>
|
|
</q-form>
|
|
</q-card>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
<script src="{{ static_url_for('static', 'js/node.js') }}"></script>
|
|
<script>
|
|
Vue.component(VueQrcode.name, VueQrcode)
|
|
Vue.use(VueQrcodeReader)
|
|
|
|
new Vue({
|
|
el: '#vue',
|
|
config: {
|
|
globalProperties: {
|
|
LNbits,
|
|
msg: 'hello'
|
|
}
|
|
},
|
|
mixins: [windowMixin],
|
|
data: function () {
|
|
return {
|
|
isSuperUser: false,
|
|
wallet: {},
|
|
tab: 'dashboard',
|
|
payments: 1000,
|
|
info: {},
|
|
channel_stats: {},
|
|
|
|
channels: {
|
|
data: [],
|
|
filter: ''
|
|
},
|
|
|
|
activeBalance: {},
|
|
ranks: {},
|
|
|
|
peers: {
|
|
data: [],
|
|
filter: ''
|
|
},
|
|
|
|
connectPeerDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
openChannelDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
closeChannelDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
nodeInfoDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
transactionDetailsDialog: {
|
|
show: false,
|
|
data: {}
|
|
},
|
|
|
|
states: [
|
|
{label: 'Active', value: 'active', color: 'green'},
|
|
{label: 'Pending', value: 'pending', color: 'orange'},
|
|
{label: 'Inactive', value: 'inactive', color: 'grey'},
|
|
{label: 'Closed', value: 'closed', color: 'red'}
|
|
],
|
|
|
|
stateFilters: [
|
|
{label: 'Active', value: 'active'},
|
|
{label: 'Pending', value: 'pending'}
|
|
],
|
|
|
|
paymentsTable: {
|
|
data: [],
|
|
columns: [
|
|
{
|
|
name: 'pending',
|
|
label: ''
|
|
},
|
|
{
|
|
name: 'memo',
|
|
align: 'left',
|
|
label: this.$t('memo'),
|
|
field: 'memo'
|
|
},
|
|
{
|
|
name: 'date',
|
|
align: 'left',
|
|
label: this.$t('date'),
|
|
field: 'date',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'sat',
|
|
align: 'right',
|
|
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
|
field: row => this.formatMsat(row.amount),
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'fee',
|
|
align: 'right',
|
|
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
|
|
field: 'fee'
|
|
},
|
|
{
|
|
name: 'destination',
|
|
align: 'right',
|
|
label: 'Destination',
|
|
field: 'destination'
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10,
|
|
page: 1,
|
|
rowsNumber: 10
|
|
},
|
|
filter: null
|
|
},
|
|
invoiceTable: {
|
|
data: [],
|
|
columns: [
|
|
{
|
|
name: 'pending',
|
|
label: ''
|
|
},
|
|
{
|
|
name: 'memo',
|
|
align: 'left',
|
|
label: this.$t('memo'),
|
|
field: 'memo'
|
|
},
|
|
{
|
|
name: 'paid_at',
|
|
field: 'paid_at',
|
|
align: 'right',
|
|
label: 'Paid at',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'expiry',
|
|
label: this.$t('expiry'),
|
|
field: 'expiry',
|
|
align: 'right',
|
|
sortable: true
|
|
},
|
|
{
|
|
name: 'amount',
|
|
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
|
field: row => this.formatMsat(row.amount),
|
|
sortable: true
|
|
}
|
|
],
|
|
pagination: {
|
|
rowsPerPage: 10,
|
|
page: 1,
|
|
rowsNumber: 10
|
|
},
|
|
filter: null
|
|
}
|
|
}
|
|
},
|
|
created: function () {
|
|
this.getInfo()
|
|
this.get1MLStats()
|
|
},
|
|
watch: {
|
|
tab: function (val) {
|
|
if (val === 'transactions' && !this.paymentsTable.data.length) {
|
|
this.getPayments()
|
|
this.getInvoices()
|
|
} else if (val === 'channels' && !this.channels.data.length) {
|
|
this.getChannels()
|
|
this.getPeers()
|
|
}
|
|
}
|
|
},
|
|
computed: {
|
|
checkChanges() {
|
|
return !_.isEqual(this.settings, this.formData)
|
|
},
|
|
filteredChannels: function () {
|
|
return this.stateFilters
|
|
? this.channels.data.filter(channel => {
|
|
return this.stateFilters.find(({value}) => value == channel.state)
|
|
})
|
|
: this.channels.data
|
|
},
|
|
totalBalance: function () {
|
|
return this.filteredChannels.reduce(
|
|
(balance, channel) => {
|
|
balance.local_msat += channel.balance.local_msat
|
|
balance.remote_msat += channel.balance.remote_msat
|
|
balance.total_msat += channel.balance.total_msat
|
|
return balance
|
|
},
|
|
{local_msat: 0, remote_msat: 0, total_msat: 0}
|
|
)
|
|
}
|
|
},
|
|
methods: {
|
|
formatMsat: function (msat) {
|
|
return LNbits.utils.formatMsat(msat)
|
|
},
|
|
api: function (method, url, options) {
|
|
const params = new URLSearchParams(options?.query)
|
|
return LNbits.api
|
|
.request(method, `/node/api/v1${url}?${params}`, {}, options?.data)
|
|
.catch(error => {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
},
|
|
getChannels: function () {
|
|
return this.api('GET', '/channels').then(response => {
|
|
this.channels.data = response.data
|
|
})
|
|
},
|
|
getInfo: function () {
|
|
return this.api('GET', '/info').then(response => {
|
|
this.info = response.data
|
|
this.channel_stats = response.data.channel_stats
|
|
})
|
|
},
|
|
get1MLStats: function () {
|
|
return this.api('GET', '/rank').then(response => {
|
|
this.ranks = response.data
|
|
})
|
|
},
|
|
getPayments: function (props) {
|
|
if (props) {
|
|
this.paymentsTable.pagination = props.pagination
|
|
}
|
|
let pagination = this.paymentsTable.pagination
|
|
const query = {
|
|
limit: pagination.rowsPerPage,
|
|
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
|
}
|
|
return this.api('GET', '/payments', {query}).then(response => {
|
|
this.paymentsTable.data = response.data.data
|
|
this.paymentsTable.pagination.rowsNumber = response.data.total
|
|
})
|
|
},
|
|
getInvoices: function (props) {
|
|
if (props) {
|
|
this.invoiceTable.pagination = props.pagination
|
|
}
|
|
let pagination = this.invoiceTable.pagination
|
|
const query = {
|
|
limit: pagination.rowsPerPage,
|
|
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
|
}
|
|
return this.api('GET', '/invoices', {query}).then(response => {
|
|
this.invoiceTable.data = response.data.data
|
|
this.invoiceTable.pagination.rowsNumber = response.data.total
|
|
})
|
|
},
|
|
getPeers: function () {
|
|
return this.api('GET', '/peers').then(response => {
|
|
this.peers.data = response.data
|
|
console.log('peers', this.peers)
|
|
})
|
|
},
|
|
connectPeer: function () {
|
|
console.log('peer', this.connectPeerDialog)
|
|
this.api('POST', '/peers', {data: this.connectPeerDialog.data}).then(
|
|
() => {
|
|
this.connectPeerDialog.show = false
|
|
this.getPeers()
|
|
}
|
|
)
|
|
},
|
|
disconnectPeer: function (id) {
|
|
LNbits.utils
|
|
.confirmDialog('Do you really wanna disconnect this peer?')
|
|
.onOk(() => {
|
|
this.api('DELETE', `/peers/${id}`).then(response => {
|
|
this.$q.notify({
|
|
message: 'Disconnected',
|
|
icon: null
|
|
})
|
|
this.needsRestart = true
|
|
this.getPeers()
|
|
})
|
|
})
|
|
},
|
|
openChannel: function () {
|
|
this.api('POST', '/channels', {data: this.openChannelDialog.data})
|
|
.then(response => {
|
|
this.openChannelDialog.show = false
|
|
this.getChannels()
|
|
})
|
|
.catch(error => {
|
|
console.log(error)
|
|
})
|
|
},
|
|
showCloseChannelDialog: function (channel) {
|
|
this.closeChannelDialog.show = true
|
|
this.closeChannelDialog.data = {
|
|
force: false,
|
|
short_id: channel.short_id,
|
|
...channel.point
|
|
}
|
|
},
|
|
closeChannel: function () {
|
|
this.api('DELETE', '/channels', {
|
|
query: this.closeChannelDialog.data
|
|
}).then(response => {
|
|
this.closeChannelDialog.show = false
|
|
this.getChannels()
|
|
})
|
|
},
|
|
showOpenChannelDialog: function (peer_id) {
|
|
this.openChannelDialog.show = true
|
|
this.openChannelDialog.data = {peer_id, funding_amount: 0}
|
|
},
|
|
showNodeInfoDialog: function (node) {
|
|
this.nodeInfoDialog.show = true
|
|
this.nodeInfoDialog.data = node
|
|
},
|
|
showTransactionDetailsDialog: function (details) {
|
|
this.transactionDetailsDialog.show = true
|
|
this.transactionDetailsDialog.data = details
|
|
console.log('details', details)
|
|
},
|
|
exportCSV: function () {},
|
|
shortenNodeId
|
|
}
|
|
})
|
|
</script>
|
|
|
|
{% endblock %}
|