Wallet polish (#2942)

This commit is contained in:
Vlad Stan
2025-02-11 15:19:41 +02:00
committed by GitHub
parent 4d490506f3
commit 2d41a1bed3
17 changed files with 565 additions and 311 deletions
+2 -2
View File
@@ -397,8 +397,8 @@ async def get_daily_stats(
SUM(apipayments.amount - ABS(apipayments.fee)) AS balance,
ABS(SUM(apipayments.fee)) as fee,
COUNT(*) as payments_count
FROM apipayments
RIGHT JOIN wallets ON apipayments.wallet_id = wallets.id
FROM wallets
LEFT JOIN apipayments ON apipayments.wallet_id = wallets.id
{clause}
AND (wallets.deleted = false OR wallets.deleted is NULL)
GROUP BY date
+1 -1
View File
@@ -125,7 +125,7 @@ class Payment(BaseModel):
class PaymentFilters(FilterModel):
__search_fields__ = ["memo", "amount", "wallet_id", "tag"]
__search_fields__ = ["memo", "amount", "wallet_id", "tag", "status", "time"]
__sort_fields__ = ["created_at", "amount", "fee", "memo", "time", "tag"]
+11 -8
View File
@@ -399,14 +399,17 @@ async def calculate_fiat_amounts(
amount_sat = int(amount)
if wallet_currency:
if wallet_currency == currency:
fiat_amount = amount
else:
fiat_amount = await satoshis_amount_as_fiat(amount_sat, wallet_currency)
fiat_amounts["wallet_fiat_currency"] = wallet_currency
fiat_amounts["wallet_fiat_amount"] = round(fiat_amount, ndigits=3)
fiat_amounts["wallet_fiat_rate"] = amount_sat / fiat_amount
fiat_amounts["wallet_btc_rate"] = (fiat_amount / amount_sat) * 100_000_000
try:
if wallet_currency == currency:
fiat_amount = amount
else:
fiat_amount = await satoshis_amount_as_fiat(amount_sat, wallet_currency)
fiat_amounts["wallet_fiat_currency"] = wallet_currency
fiat_amounts["wallet_fiat_amount"] = round(fiat_amount, ndigits=3)
fiat_amounts["wallet_fiat_rate"] = amount_sat / fiat_amount
fiat_amounts["wallet_btc_rate"] = (fiat_amount / amount_sat) * 100_000_000
except Exception as e:
logger.error(f"Error calculating fiat amount for wallet '{wallet.id}': {e}")
logger.debug(
f"Calculated fiat amounts {wallet.id=} {amount=} {currency=}: {fiat_amounts=}"
@@ -199,6 +199,7 @@
v-model="formData.lnbits_default_bgimage"
label="Background Image"
@update:model-value="applyGlobalBgimage"
hint="This must be a trusted source. It can change the content and it can log your IP address."
>
</q-input>
</div>
+192 -93
View File
@@ -48,7 +48,7 @@
'q-pt-sm': g.fiatTracking,
'q-pt-lg': !g.fiatTracking
}"
v-if="!isPrioritySwapped || !g.fiatTracking"
v-if="!isFiatPriority || !g.fiatTracking"
style="height: 100px"
>
<div class="col-7">
@@ -82,7 +82,7 @@
<div
class="column"
v-if="isPrioritySwapped && g.fiatTracking"
v-if="isFiatPriority && g.fiatTracking"
:class="{
'q-pt-sm': g.fiatTracking,
'q-pt-lg': !g.fiatTracking
@@ -189,6 +189,7 @@
>
<q-card-section>
<payment-list
@filter-changed="handleFilterChange"
:update="updatePayments"
:mobile-simple="mobileSimple"
:expand-details="expandDetails"
@@ -256,114 +257,191 @@
<q-expansion-item
group="extras"
icon="settings_cell"
:label="$t('export_to_phone')"
>
<q-card>
<q-card-section class="text-center">
<p v-text="$t('export_to_phone_desc')"></p>
<lnbits-qrcode
:value="`${baseUrl}/wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
></lnbits-qrcode>
</q-card-section>
<span v-text="exportWalletQR"></span>
<q-card-actions class="flex-center q-pb-md">
<q-btn
outline
color="grey"
:label="$t('copy_wallet_url')"
@click="copyText(`${baseUrl}/wallet?usr=${g.user.id}&wal=${g.wallet.id}`)"
></q-btn>
</q-card-actions>
</q-card>
</q-expansion-item>
<q-separator></q-separator>
<q-expansion-item
group="extras"
icon="edit"
:label="$t('rename_wallet')"
icon="phone_android"
:label="$t('access_wallet_on_mobile')"
>
<q-card>
<q-card-section>
<div class="" style="max-width: 320px">
<q-input
filled
v-model.trim="update.name"
label="Name"
dense
/>
</div>
<q-btn
:disable="!update.name.length"
unelevated
class="q-mt-sm"
color="primary"
:label="$t('update_name')"
@click="updateWallet({ name: update.name })"
></q-btn>
You can connect to this wallet from a mobile app:
<ul>
<li>
Download
<a class="text-secondary" href="https://zeusln.app"
>Zeus</a
>
or
<a
class="text-secondary"
href="https://bluewallet.io/"
>BlueWallet</a
>
from App Store or Google Play
</li>
<li>
Enable the
<a class="text-secondary" href="/lndhub">LndHub </a>
extension for this account
</li>
<li>
Scan the QR code in the
<a class="text-secondary" href="/lndhub">LndHub </a>
extensions with your mobile app
</li>
</ul>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator></q-separator>
<q-expansion-item
group="extras"
icon="attach_money"
:label="$t('fiat_tracking')"
>
<q-card>
<q-card-section>
<div style="max-width: 360px">
<div class="row">
<div class="col">
<q-select
filled
dense
v-model="update.currency"
type="text"
:disable="g.fiatTracking"
:options="receive.units.filter((u) => u !== 'sat')"
></q-select>
</div>
<div class="col-auto">
Or you can access the wallet directly from your mobile
browser using:
<q-expansion-item
icon="mobile_friendly"
:label="$t('export_to_phone')"
>
<q-card>
<q-card-section class="text-center">
<p v-text="$t('export_to_phone_desc')"></p>
<lnbits-qrcode
:value="`${baseUrl}/wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
></lnbits-qrcode>
</q-card-section>
<span v-text="exportWalletQR"></span>
<q-card-actions class="flex-center q-pb-md">
<q-btn
color="primary"
@click="handleFiatTracking()"
:disable="update.currency == ''"
:label="g.fiatTracking ? 'Remove' : 'Add'"
outline
color="grey"
:label="$t('copy_wallet_url')"
@click="copyText(`${baseUrl}wallet?usr=${g.user.id}&wal=${g.wallet.id}`)"
></q-btn>
</div>
</q-card-actions>
</q-card>
</q-expansion-item>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator></q-separator>
<q-expansion-item
group="extras"
icon="settings"
:label="$t('wallet_config')"
>
<q-card>
<q-card-section>
<div class="row">
<div class="col-6">
<q-input
filled
v-model.trim="update.name"
label="Name"
dense
/>
</div>
<div class="col-4 q-pl-sm">
<q-btn
:disable="!update.name.length"
unelevated
class="q-mt-xs full-width"
color="primary"
:label="$t('update_name')"
dense
@click="updateWallet({ name: update.name })"
></q-btn>
</div>
<div class="col-2"></div>
</div>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
<q-select
filled
dense
v-model="update.currency"
type="text"
:disable="g.fiatTracking"
:options="receive.units.filter((u) => u !== 'sat')"
></q-select>
</div>
<div class="col-4 q-pl-sm">
<q-btn
dense
color="primary"
class="q-mt-xs full-width"
@click="handleFiatTracking()"
:disable="update.currency == ''"
:label="g.fiatTracking ? 'Remove' : 'Add'"
></q-btn>
</div>
<div class="col-2">
<q-btn
v-if="g.user.admin"
flat
round
icon="settings"
class="float-right q-mb-lg"
to="/admin#exchange_providers"
><q-tooltip
v-text="$t('exchange_providers')"
></q-tooltip
></q-btn>
</div>
</div>
<q-btn
v-if="g.user.admin"
class="absolute-top-right"
flat
round
icon="settings"
to="/admin#exchange_providers"
><q-tooltip
v-text="$t('exchange_providers')"
></q-tooltip
></q-btn>
</q-card-section>
<q-card-section>
<div class="row">
<div class="col-6">
<p v-text="$t('delete_wallet_desc')"></p>
</div>
<div class="col-4 q-pl-sm">
<q-btn
unelevated
color="red-10"
class="full-width"
@click="deleteWallet()"
:label="$t('delete_wallet')"
></q-btn>
</div>
<div class="col-2"></div>
</div>
</q-card-section>
</q-card>
</q-expansion-item>
<q-separator></q-separator>
<q-expansion-item
group="extras"
icon="remove_circle"
:label="$t('delete_wallet')"
group="charts"
icon="insights"
:label="$t('wallet_charts')"
>
<q-card>
<q-card-section>
<p v-text="$t('delete_wallet_desc')"></p>
<q-btn
unelevated
color="red-10"
@click="deleteWallet()"
:label="$t('delete_wallet')"
></q-btn>
<div class="row">
<div class="col-md-4 col-sm-12">
<q-checkbox
dense
@click="saveChartsPreferences"
v-model="chartConfig.showBalance"
:label="$t('payments_balance_chart')"
>
</q-checkbox>
</div>
<div class="col-md-4 col-sm-12">
<q-checkbox
dense
@click="saveChartsPreferences"
v-model="chartConfig.showBalanceInOut"
:label="$t('payments_balance_in_out_chart')"
>
</q-checkbox>
</div>
<div class="col-md-4 col-sm-12">
<q-checkbox
dense
@click="saveChartsPreferences"
v-model="chartConfig.showPaymentCountInOut"
:label="$t('payments_count_in_out_chart')"
>
</q-checkbox>
</div>
</div>
</q-card-section>
</q-card>
</q-expansion-item>
@@ -403,6 +481,27 @@
>{% endfor %}
</q-card>
{% endif %}
<q-card v-if="chartConfig.showBalance">
<q-card-section class="q-pa-none">
<div style="height: 200px" class="q-pa-sm">
<canvas ref="walletBalanceChart"></canvas>
</div>
</q-card-section>
</q-card>
<q-card v-if="chartConfig.showBalanceInOut">
<q-card-section class="q-pa-none">
<div style="height: 200px" class="q-pa-sm">
<canvas ref="walletBalanceInOut"></canvas>
</div>
</q-card-section>
</q-card>
<q-card v-if="chartConfig.showPaymentCountInOut">
<q-card-section class="q-pa-none">
<div style="height: 200px" class="q-pa-sm">
<canvas ref="walletPaymentsInOut"></canvas>
</div>
</q-card-section>
</q-card>
</div>
</div>
+19 -1
View File
@@ -35,11 +35,13 @@ from lnbits.core.models import (
PaymentWalletStats,
Wallet,
)
from lnbits.core.models.users import User
from lnbits.core.services.payments import get_payments_daily_stats
from lnbits.db import Filters, Page
from lnbits.decorators import (
WalletTypeInfo,
check_admin,
check_user_exists,
parse_filters,
require_admin_key,
require_invoice_key,
@@ -135,13 +137,29 @@ async def api_payments_wallets_stats(
@payment_router.get(
"/stats/daily",
name="Get payments history per day",
dependencies=[Depends(check_admin)],
response_model=List[PaymentDailyStats],
openapi_extra=generate_filter_params_openapi(PaymentFilters),
)
async def api_payments_daily_stats(
user: User = Depends(check_user_exists),
filters: Filters[PaymentFilters] = Depends(parse_filters(PaymentFilters)),
):
if not user.admin:
exc = HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
detail="Missing wallet id.",
)
wallet_filter = next(
(f for f in filters.filters if f.field == "wallet_id"), None
)
if not wallet_filter:
raise exc
wallet_id = list((wallet_filter.values or {}).values())
if len(wallet_id) == 0:
raise exc
if not user.get_wallet(wallet_id[0]):
raise exc
return await get_payments_daily_stats(filters)