Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b4d9bf5db | ||
|
|
c51e7351e8 | ||
|
|
fe12eccc42 | ||
|
|
17b7826753 | ||
|
|
14ae6d8b1a | ||
|
|
7b559991c7 | ||
|
|
decd4cdf0a | ||
|
|
dc7a6551c5 | ||
|
|
526467747e | ||
|
|
b8d295a5b7 | ||
|
|
293b6267be | ||
|
|
20e70854a1 | ||
|
|
11173e6460 | ||
|
|
069a7922fc | ||
|
|
98338ffb00 | ||
|
|
62dec118af | ||
|
|
84b406534d | ||
|
|
d9d2d59b73 | ||
|
|
20d4b954c0 | ||
|
|
5edb845d8f | ||
|
|
cfe2b43d2a | ||
|
|
a0ac41a9ce | ||
|
|
194a527736 | ||
|
|
6f135c0696 | ||
|
|
9a767b627f | ||
|
|
c1c4eda69d | ||
|
|
6c46867698 | ||
|
|
a73eb569ae |
+40
-24
@@ -1,5 +1,28 @@
|
|||||||
#For more information on .env files, their content and format: https://pypi.org/project/python-dotenv/
|
#For more information on .env files, their content and format: https://pypi.org/project/python-dotenv/
|
||||||
|
|
||||||
|
######################################
|
||||||
|
########### Admin Settings ###########
|
||||||
|
######################################
|
||||||
|
|
||||||
|
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available.
|
||||||
|
# Warning: Enabling this will make LNbits ignore most configurations in file. Only the
|
||||||
|
# configurations defined in `ReadOnlySettings` will still be read from the environment variables.
|
||||||
|
# The rest of the settings will be stored in your database and you will be able to change them
|
||||||
|
# only through the Admin UI.
|
||||||
|
# Disable this to make LNbits use this config file again.
|
||||||
|
LNBITS_ADMIN_UI=false
|
||||||
|
|
||||||
|
# Change theme
|
||||||
|
LNBITS_SITE_TITLE="LNbits"
|
||||||
|
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
|
||||||
|
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
|
||||||
|
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber
|
||||||
|
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber"
|
||||||
|
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
||||||
|
|
||||||
|
HOST=127.0.0.1
|
||||||
|
PORT=5000
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
########## Funding Source ############
|
########## Funding Source ############
|
||||||
######################################
|
######################################
|
||||||
@@ -78,48 +101,34 @@ ECLAIR_PASS=eclairpw
|
|||||||
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
||||||
LNTIPS_API_ENDPOINT=https://ln.tips
|
LNTIPS_API_ENDPOINT=https://ln.tips
|
||||||
|
|
||||||
######################################
|
|
||||||
########### Admin Settings ###########
|
|
||||||
######################################
|
|
||||||
|
|
||||||
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
|
|
||||||
# Warning: Enabling this will make LNbits ignore this configuration file. Your settings will
|
|
||||||
# be stored in your database and you will be able to change them only through the Admin UI.
|
|
||||||
# Disable this to make LNbits use this config file again.
|
|
||||||
LNBITS_ADMIN_UI=false
|
|
||||||
|
|
||||||
# Change theme
|
|
||||||
LNBITS_SITE_TITLE="LNbits"
|
|
||||||
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
|
|
||||||
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
|
|
||||||
# Choose from bitcoin, mint, flamingo, freedom, salvador, autumn, monochrome, classic, cyber
|
|
||||||
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador, cyber"
|
|
||||||
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
|
|
||||||
|
|
||||||
HOST=127.0.0.1
|
|
||||||
PORT=5000
|
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
####### Auth Configurations ##########
|
####### Auth Configurations ##########
|
||||||
######################################
|
######################################
|
||||||
# Secret Key: will default to the hash of the super user. It is strongly recommended that you set your own value.
|
# Secret Key: will default to the hash of the super user. It is strongly recommended that you set your own value.
|
||||||
AUTH_SECRET_KEY=""
|
AUTH_SECRET_KEY=""
|
||||||
AUTH_TOKEN_EXPIRE_MINUTES=525600
|
AUTH_TOKEN_EXPIRE_MINUTES=525600
|
||||||
# Possible authorization methods: user-id-only, username-password, google-auth, github-auth
|
# Possible authorization methods: user-id-only, username-password, google-auth, github-auth, keycloak-auth
|
||||||
AUTH_ALLOWED_METHODS="user-id-only, username-password"
|
AUTH_ALLOWED_METHODS="user-id-only, username-password"
|
||||||
# Set this flag if HTTP is used for OAuth
|
# Set this flag if HTTP is used for OAuth
|
||||||
# OAUTHLIB_INSECURE_TRANSPORT="1"
|
# OAUTHLIB_INSECURE_TRANSPORT="1"
|
||||||
|
|
||||||
# Google OAuth Config
|
# Google OAuth Config
|
||||||
# Make sure thant the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token
|
# Make sure that the authorized redirect URIs contain https://{domain}/api/v1/auth/google/token
|
||||||
GOOGLE_CLIENT_ID=""
|
GOOGLE_CLIENT_ID=""
|
||||||
GOOGLE_CLIENT_SECRET=""
|
GOOGLE_CLIENT_SECRET=""
|
||||||
|
|
||||||
# GitHub OAuth Config
|
# GitHub OAuth Config
|
||||||
# Make sure thant the authorization callback URL is set to https://{domain}/api/v1/auth/github/token
|
# Make sure that the authorization callback URL is set to https://{domain}/api/v1/auth/github/token
|
||||||
GITHUB_CLIENT_ID=""
|
GITHUB_CLIENT_ID=""
|
||||||
GITHUB_CLIENT_SECRET=""
|
GITHUB_CLIENT_SECRET=""
|
||||||
|
|
||||||
|
# Keycloak OAuth Config
|
||||||
|
# Make sure that the valid redirect URIs contain https://{domain}/api/v1/auth/keycloak/token
|
||||||
|
KEYCLOAK_CLIENT_ID=""
|
||||||
|
KEYCLOAK_CLIENT_SECRET=""
|
||||||
|
KEYCLOAK_DISCOVERY_URL=""
|
||||||
|
|
||||||
|
|
||||||
######################################
|
######################################
|
||||||
|
|
||||||
# uvicorn variable, uncomment to allow https behind a proxy
|
# uvicorn variable, uncomment to allow https behind a proxy
|
||||||
@@ -198,6 +207,13 @@ LNBITS_RESERVE_FEE_MIN=2000
|
|||||||
# value in percent
|
# value in percent
|
||||||
LNBITS_RESERVE_FEE_PERCENT=1.0
|
LNBITS_RESERVE_FEE_PERCENT=1.0
|
||||||
|
|
||||||
|
# limit the maximum balance for each wallet
|
||||||
|
# throw an error if the wallet attempts to create a new invoice
|
||||||
|
|
||||||
|
# LNBITS_WALLET_LIMIT_MAX_BALANCE=1000000
|
||||||
|
# LNBITS_WALLET_LIMIT_DAILY_MAX_WITHDRAW=1000000
|
||||||
|
# LNBITS_WALLET_LIMIT_SECS_BETWEEN_TRANS=60
|
||||||
|
|
||||||
# Limit fiat currencies allowed to see in UI
|
# Limit fiat currencies allowed to see in UI
|
||||||
# LNBITS_ALLOWED_CURRENCIES="EUR, USD"
|
# LNBITS_ALLOWED_CURRENCIES="EUR, USD"
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
name: docker
|
name: docker
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
default: latest
|
||||||
|
type: string
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
@@ -38,7 +43,7 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits:${{ inputs.tag }}
|
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits:${{ inputs.tag }}
|
||||||
platforms: [ linux/amd64, linux/arm64 ]
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
|
||||||
@@ -48,6 +53,6 @@ jobs:
|
|||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits:latest
|
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits:latest
|
||||||
platforms: [ linux/amd64, linux/arm64 ]
|
platforms: linux/amd64,linux/arm64
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
push: false
|
push: false
|
||||||
tags: lnbitsdocker/lnbits-legend:latest
|
tags: lnbits/lnbits:latest
|
||||||
cache-from: type=registry,ref=lnbitsdocker/lnbits-legend:latest
|
cache-from: type=registry,ref=lnbits/lnbits:latest
|
||||||
cache-to: type=inline
|
cache-to: type=inline
|
||||||
|
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
@@ -51,7 +51,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Create fake admin
|
- name: Create fake admin
|
||||||
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
|
||||||
run: docker exec lnbits-legend-lnbits-1 poetry run python tools/create_fake_admin.py
|
run: docker exec lnbits-lnbits-1 poetry run python tools/create_fake_admin.py
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ jobs:
|
|||||||
release:
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
- name: Create github release
|
- name: Create github release
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ FROM python:3.10-slim-bullseye
|
|||||||
|
|
||||||
RUN apt-get clean
|
RUN apt-get clean
|
||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install -y curl pkg-config build-essential
|
RUN apt-get install -y curl pkg-config build-essential libnss-myhostname
|
||||||
|
|
||||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|||||||
@@ -101,19 +101,19 @@ SUPER_USER=be54db7f245346c8833eaa430e1e0405 LNBITS_ADMIN_UI=true ./result/bin/ln
|
|||||||
|
|
||||||
use latest version from docker hub
|
use latest version from docker hub
|
||||||
```sh
|
```sh
|
||||||
docker pull lnbitsdocker/lnbits-legend
|
docker pull lnbits/lnbits
|
||||||
wget https://raw.githubusercontent.com/lnbits/lnbits/main/.env.example -O .env
|
wget https://raw.githubusercontent.com/lnbits/lnbits/main/.env.example -O .env
|
||||||
mkdir data
|
mkdir data
|
||||||
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbitsdocker/lnbits-legend
|
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
|
||||||
```
|
```
|
||||||
build the image yourself
|
build the image yourself
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/lnbits/lnbits.git
|
git clone https://github.com/lnbits/lnbits.git
|
||||||
cd lnbits
|
cd lnbits
|
||||||
docker build -t lnbitsdocker/lnbits-legend .
|
docker build -t lnbits/lnbits .
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
mkdir data
|
mkdir data
|
||||||
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbitsdocker/lnbits-legend
|
docker run --detach --publish 5000:5000 --name lnbits --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits/lnbits
|
||||||
```
|
```
|
||||||
|
|
||||||
## Option 4: Fly.io
|
## Option 4: Fly.io
|
||||||
|
|||||||
+3
-3
@@ -23,7 +23,7 @@ from slowapi.util import get_remote_address
|
|||||||
from starlette.middleware.sessions import SessionMiddleware
|
from starlette.middleware.sessions import SessionMiddleware
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
|
|
||||||
from lnbits.core.crud import get_installed_extensions
|
from lnbits.core.crud import get_dbversions, get_installed_extensions
|
||||||
from lnbits.core.helpers import migrate_extension_database
|
from lnbits.core.helpers import migrate_extension_database
|
||||||
from lnbits.core.services import websocketUpdater
|
from lnbits.core.services import websocketUpdater
|
||||||
from lnbits.core.tasks import ( # register_watchdog,; unregister_watchdog,
|
from lnbits.core.tasks import ( # register_watchdog,; unregister_watchdog,
|
||||||
@@ -35,7 +35,7 @@ from lnbits.tasks import cancel_all_tasks, create_permanent_task
|
|||||||
from lnbits.utils.cache import cache
|
from lnbits.utils.cache import cache
|
||||||
from lnbits.wallets import get_wallet_class, set_wallet_class
|
from lnbits.wallets import get_wallet_class, set_wallet_class
|
||||||
|
|
||||||
from .commands import db_versions, migrate_databases
|
from .commands import migrate_databases
|
||||||
from .core import init_core_routers
|
from .core import init_core_routers
|
||||||
from .core.db import core_app_extra
|
from .core.db import core_app_extra
|
||||||
from .core.services import check_admin_settings, check_webpush_settings
|
from .core.services import check_admin_settings, check_webpush_settings
|
||||||
@@ -273,7 +273,7 @@ async def restore_installed_extension(app: FastAPI, ext: InstallableExtension):
|
|||||||
extension = Extension.from_installable_ext(ext)
|
extension = Extension.from_installable_ext(ext)
|
||||||
register_ext_routes(app, extension)
|
register_ext_routes(app, extension)
|
||||||
|
|
||||||
current_version = (await db_versions()).get(ext.id, 0)
|
current_version = (await get_dbversions()).get(ext.id, 0)
|
||||||
await migrate_extension_database(extension, current_version)
|
await migrate_extension_database(extension, current_version)
|
||||||
|
|
||||||
# mount routes for the new version
|
# mount routes for the new version
|
||||||
|
|||||||
@@ -1097,6 +1097,8 @@ async def get_admin_settings(is_super_user: bool = False) -> Optional[AdminSetti
|
|||||||
return None
|
return None
|
||||||
row_dict = dict(sets)
|
row_dict = dict(sets)
|
||||||
row_dict.pop("super_user")
|
row_dict.pop("super_user")
|
||||||
|
row_dict.pop("auth_all_methods")
|
||||||
|
|
||||||
admin_settings = AdminSettings(
|
admin_settings = AdminSettings(
|
||||||
is_super_user=is_super_user,
|
is_super_user=is_super_user,
|
||||||
lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
|
lnbits_allowed_funding_sources=settings.lnbits_allowed_funding_sources,
|
||||||
|
|||||||
+42
-1
@@ -53,8 +53,45 @@ async def stop_extension_background_work(
|
|||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
|
Stop background work for extension (like asyncio.Tasks, WebSockets, etc).
|
||||||
Extensions SHOULD expose a DELETE enpoint at the root level of their API.
|
Extensions SHOULD expose a `api_stop()` function and/or a DELETE enpoint
|
||||||
|
at the root level of their API.
|
||||||
"""
|
"""
|
||||||
|
stopped = await _stop_extension_background_work(ext_id)
|
||||||
|
|
||||||
|
if not stopped:
|
||||||
|
# fallback to REST API call
|
||||||
|
await _stop_extension_background_work_via_api(ext_id, user, access_token)
|
||||||
|
|
||||||
|
|
||||||
|
async def _stop_extension_background_work(ext_id) -> bool:
|
||||||
|
upgrade_hash = settings.extension_upgrade_hash(ext_id) or ""
|
||||||
|
ext = Extension(ext_id, True, False, upgrade_hash=upgrade_hash)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info(f"Stopping background work for extension '{ext.module_name}'.")
|
||||||
|
old_module = importlib.import_module(ext.module_name)
|
||||||
|
|
||||||
|
# Extensions must expose an `{ext_id}_stop()` function at the module level
|
||||||
|
# The `api_stop()` function is for backwards compatibility (will be deprecated)
|
||||||
|
stop_fns = [f"{ext_id}_stop", "api_stop"]
|
||||||
|
stop_fn_name = next((fn for fn in stop_fns if hasattr(old_module, fn)), None)
|
||||||
|
assert stop_fn_name, "No stop function found for '{ext.module_name}'"
|
||||||
|
|
||||||
|
await getattr(old_module, stop_fn_name)()
|
||||||
|
|
||||||
|
logger.info(f"Stopped background work for extension '{ext.module_name}'.")
|
||||||
|
except Exception as ex:
|
||||||
|
logger.warning(f"Failed to stop background work for '{ext.module_name}'.")
|
||||||
|
logger.warning(ex)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def _stop_extension_background_work_via_api(ext_id, user, access_token):
|
||||||
|
logger.info(
|
||||||
|
f"Stopping background work for extension '{ext_id}' using the REST API."
|
||||||
|
)
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
url = f"http://{settings.host}:{settings.port}/{ext_id}/api/v1?usr={user}"
|
url = f"http://{settings.host}:{settings.port}/{ext_id}/api/v1?usr={user}"
|
||||||
@@ -63,7 +100,11 @@ async def stop_extension_background_work(
|
|||||||
)
|
)
|
||||||
resp = await client.delete(url=url, headers=headers)
|
resp = await client.delete(url=url, headers=headers)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
|
logger.info(f"Stopped background work for extension '{ext_id}'.")
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to stop background work for '{ext_id}' using the REST API."
|
||||||
|
)
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+67
-2
@@ -1,6 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Tuple, TypedDict
|
from typing import Dict, List, Optional, Tuple, TypedDict
|
||||||
@@ -41,6 +42,7 @@ from .crud import (
|
|||||||
create_wallet,
|
create_wallet,
|
||||||
delete_wallet_payment,
|
delete_wallet_payment,
|
||||||
get_account,
|
get_account,
|
||||||
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
get_super_settings,
|
get_super_settings,
|
||||||
get_total_balance,
|
get_total_balance,
|
||||||
@@ -118,8 +120,9 @@ async def create_invoice(
|
|||||||
if not amount > 0:
|
if not amount > 0:
|
||||||
raise InvoiceFailure("Amountless invoices not supported.")
|
raise InvoiceFailure("Amountless invoices not supported.")
|
||||||
|
|
||||||
if await get_wallet(wallet_id, conn=conn) is None:
|
user_wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
raise InvoiceFailure("Wallet does not exist.")
|
if not user_wallet:
|
||||||
|
raise InvoiceFailure(f"Could not fetch wallet '{wallet_id}'.")
|
||||||
|
|
||||||
invoice_memo = None if description_hash else memo
|
invoice_memo = None if description_hash else memo
|
||||||
|
|
||||||
@@ -130,6 +133,14 @@ async def create_invoice(
|
|||||||
amount, wallet_id, currency=currency, extra=extra, conn=conn
|
amount, wallet_id, currency=currency, extra=extra, conn=conn
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if settings.is_wallet_max_balance_exceeded(
|
||||||
|
user_wallet.balance_msat / 1000 + amount_sat
|
||||||
|
):
|
||||||
|
raise InvoiceFailure(
|
||||||
|
f"Wallet balance cannot exceed "
|
||||||
|
f"{settings.lnbits_wallet_limit_max_balance} sats."
|
||||||
|
)
|
||||||
|
|
||||||
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
|
ok, checking_id, payment_request, error_message = await wallet.create_invoice(
|
||||||
amount=amount_sat,
|
amount=amount_sat,
|
||||||
memo=invoice_memo,
|
memo=invoice_memo,
|
||||||
@@ -185,6 +196,8 @@ async def pay_invoice(
|
|||||||
if max_sat and invoice.amount_msat > max_sat * 1000:
|
if max_sat and invoice.amount_msat > max_sat * 1000:
|
||||||
raise ValueError("Amount in invoice is too high.")
|
raise ValueError("Amount in invoice is too high.")
|
||||||
|
|
||||||
|
await check_wallet_limits(wallet_id, conn, invoice.amount_msat)
|
||||||
|
|
||||||
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
async with db.reuse_conn(conn) if conn else db.connect() as conn:
|
||||||
temp_id = invoice.payment_hash
|
temp_id = invoice.payment_hash
|
||||||
internal_id = f"internal_{invoice.payment_hash}"
|
internal_id = f"internal_{invoice.payment_hash}"
|
||||||
@@ -372,6 +385,58 @@ async def pay_invoice(
|
|||||||
return invoice.payment_hash
|
return invoice.payment_hash
|
||||||
|
|
||||||
|
|
||||||
|
async def check_wallet_limits(wallet_id, conn, amount_msat):
|
||||||
|
await check_time_limit_between_transactions(conn, wallet_id)
|
||||||
|
await check_wallet_daily_withdraw_limit(conn, wallet_id, amount_msat)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_time_limit_between_transactions(conn, wallet_id):
|
||||||
|
limit = settings.lnbits_wallet_limit_secs_between_trans
|
||||||
|
if not limit or limit <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
payments = await get_payments(
|
||||||
|
since=int(time.time()) - limit,
|
||||||
|
wallet_id=wallet_id,
|
||||||
|
limit=1,
|
||||||
|
conn=conn,
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(payments) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
f"The time limit of {limit} seconds between payments has been reached."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_wallet_daily_withdraw_limit(conn, wallet_id, amount_msat):
|
||||||
|
limit = settings.lnbits_wallet_limit_daily_max_withdraw
|
||||||
|
if not limit or limit <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
payments = await get_payments(
|
||||||
|
since=int(time.time()) - 60 * 60 * 24,
|
||||||
|
outgoing=True,
|
||||||
|
wallet_id=wallet_id,
|
||||||
|
limit=1,
|
||||||
|
conn=conn,
|
||||||
|
)
|
||||||
|
if len(payments) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for pay in payments:
|
||||||
|
total += pay.amount
|
||||||
|
total = total - amount_msat
|
||||||
|
if limit * 1000 + total < 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Daily withdrawal limit of "
|
||||||
|
+ str(settings.lnbits_wallet_limit_daily_max_withdraw)
|
||||||
|
+ " sats reached."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def redeem_lnurl_withdraw(
|
async def redeem_lnurl_withdraw(
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
lnurl_request: str,
|
lnurl_request: str,
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Keycloak SSO Login Helper
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from fastapi_sso.sso.base import DiscoveryDocument, OpenID, SSOBase
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakSSO(SSOBase):
|
||||||
|
"""Class providing login via Keycloak OAuth"""
|
||||||
|
|
||||||
|
provider = "keycloak"
|
||||||
|
scope = ["openid", "email", "profile"]
|
||||||
|
discovery_url = ""
|
||||||
|
|
||||||
|
async def openid_from_response(
|
||||||
|
self, response: dict, session: Optional["httpx.AsyncClient"] = None
|
||||||
|
) -> OpenID:
|
||||||
|
"""Return OpenID from user information provided by Keycloak"""
|
||||||
|
return OpenID(
|
||||||
|
email=response.get("email", ""),
|
||||||
|
provider=self.provider,
|
||||||
|
id=response.get("sub"),
|
||||||
|
first_name=response.get("given_name"),
|
||||||
|
last_name=response.get("family_name"),
|
||||||
|
display_name=response.get("name"),
|
||||||
|
picture=response.get("picture"),
|
||||||
|
)
|
||||||
|
|
||||||
|
async def get_discovery_document(self) -> DiscoveryDocument:
|
||||||
|
"""Get document containing handy urls"""
|
||||||
|
async with httpx.AsyncClient() as session:
|
||||||
|
response = await session.get(self.discovery_url)
|
||||||
|
content = response.json()
|
||||||
|
|
||||||
|
return content
|
||||||
@@ -18,16 +18,13 @@ from lnbits.core.services import (
|
|||||||
)
|
)
|
||||||
from lnbits.settings import get_wallet_class, settings
|
from lnbits.settings import get_wallet_class, settings
|
||||||
from lnbits.tasks import (
|
from lnbits.tasks import (
|
||||||
SseListenersDict,
|
|
||||||
create_permanent_task,
|
create_permanent_task,
|
||||||
create_task,
|
create_task,
|
||||||
register_invoice_listener,
|
register_invoice_listener,
|
||||||
send_push_notification,
|
send_push_notification,
|
||||||
)
|
)
|
||||||
|
|
||||||
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
|
api_invoice_listeners: Dict[str, asyncio.Queue] = {}
|
||||||
"api_invoice_listeners"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def register_killswitch():
|
def register_killswitch():
|
||||||
|
|||||||
@@ -78,6 +78,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<q-card-section
|
||||||
|
v-if="formData.auth_allowed_methods?.includes('keycloak-auth')"
|
||||||
|
class="q-pl-xl"
|
||||||
|
>
|
||||||
|
<strong class="q-my-none q-mb-sm">Keycloak Auth</strong>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 col-sm-12 q-pr-sm">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_discovery_url"
|
||||||
|
label="Keycloak Discovey URL"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-sm-12 q-pr-sm">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_client_id"
|
||||||
|
label="Keycloak Client ID"
|
||||||
|
hint="Make sure thant the authorization callback URL is set to https://{domain}/api/v1/auth/keycloak/token"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4 col-sm-12">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.keycloak_client_secret"
|
||||||
|
type="password"
|
||||||
|
label="Keycloak Client Secret"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<br />
|
<br />
|
||||||
@@ -180,6 +215,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-12">
|
<div class="col-12 col-md-12">
|
||||||
<p v-text="$t('rate_limiter')"></p>
|
<p v-text="$t('rate_limiter')"></p>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
@@ -201,6 +237,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-12">
|
||||||
|
<p v-text="$t('wallet_limiter')"></p>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="number"
|
||||||
|
v-model.number="formData.lnbits_wallet_limit_max_balance"
|
||||||
|
:label="$t('wallet_max_ballance')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="number"
|
||||||
|
v-model.number="formData.lnbits_wallet_limit_daily_max_withdraw"
|
||||||
|
:label="$t('wallet_limit_max_withdraw_per_day')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="number"
|
||||||
|
v-model.number="formData.lnbits_wallet_limit_secs_between_trans"
|
||||||
|
:label="$t('wallet_limit_secs_between_trans')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
<q-btn @click="addAdminUser" dense flat icon="add"></q-btn>
|
<q-btn @click="addAdminUser" dense flat icon="add"></q-btn>
|
||||||
</q-input>
|
</q-input>
|
||||||
<div>
|
<div>
|
||||||
{%raw%}
|
|
||||||
<q-chip
|
<q-chip
|
||||||
v-for="user in formData.lnbits_admin_users"
|
v-for="user in formData.lnbits_admin_users"
|
||||||
:key="user"
|
:key="user"
|
||||||
@@ -24,10 +23,9 @@
|
|||||||
@remove="removeAdminUser(user)"
|
@remove="removeAdminUser(user)"
|
||||||
color="primary"
|
color="primary"
|
||||||
text-color="white"
|
text-color="white"
|
||||||
|
v-text="user"
|
||||||
>
|
>
|
||||||
{{ user }}
|
|
||||||
</q-chip>
|
</q-chip>
|
||||||
{%endraw%}
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
@@ -44,7 +42,6 @@
|
|||||||
<q-btn @click="addAllowedUser" dense flat icon="add"></q-btn>
|
<q-btn @click="addAllowedUser" dense flat icon="add"></q-btn>
|
||||||
</q-input>
|
</q-input>
|
||||||
<div>
|
<div>
|
||||||
{% raw %}
|
|
||||||
<q-chip
|
<q-chip
|
||||||
v-for="user in formData.lnbits_allowed_users"
|
v-for="user in formData.lnbits_allowed_users"
|
||||||
:key="user"
|
:key="user"
|
||||||
@@ -52,12 +49,29 @@
|
|||||||
@remove="removeAllowedUser(user)"
|
@remove="removeAllowedUser(user)"
|
||||||
color="primary"
|
color="primary"
|
||||||
text-color="white"
|
text-color="white"
|
||||||
|
v-text="user"
|
||||||
>
|
>
|
||||||
{{ user }}
|
|
||||||
</q-chip>
|
</q-chip>
|
||||||
{% endraw %}
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>Allow creation of new users</q-item-label>
|
||||||
|
<q-item-label caption
|
||||||
|
>Allow creation of new users on the index page</q-item-label
|
||||||
|
>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle
|
||||||
|
size="md"
|
||||||
|
v-model="formData.lnbits_allow_new_accounts"
|
||||||
|
checked-icon="check"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
:content-inset-level="0.5"
|
:content-inset-level="0.5"
|
||||||
>
|
>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
|
<strong>Node URL: </strong><em v-text="origin"></em><br />
|
||||||
<strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br />
|
<strong>Wallet ID: </strong><em>{{ wallet.id }}</em><br />
|
||||||
<strong>Admin key: </strong><em>{{ wallet.adminkey }}</em><br />
|
<strong>Admin key: </strong><em>{{ wallet.adminkey }}</em><br />
|
||||||
<strong>Invoice/read key: </strong><em>{{ wallet.inkey }}</em>
|
<strong>Invoice/read key: </strong><em>{{ wallet.inkey }}</em>
|
||||||
|
|||||||
@@ -401,6 +401,23 @@
|
|||||||
></lnbits-notifications-btn>
|
></lnbits-notifications-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('payment_reactions')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-select
|
||||||
|
v-model="reactionChoice"
|
||||||
|
:options="reactionOptions"
|
||||||
|
label="Reactions"
|
||||||
|
@input="reactionChoiceFunc"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
><span v-text="$t('payment_reactions')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,12 +4,16 @@
|
|||||||
<div class="row q-col-gutter-md justify-center">
|
<div class="row q-col-gutter-md justify-center">
|
||||||
<div
|
<div
|
||||||
v-if="isUserAuthorized"
|
v-if="isUserAuthorized"
|
||||||
class="col-12 col-md-7 col-lg-6 q-gutter-y-md"
|
class="col-12 col-md-6 col-lg-6 q-gutter-y-md"
|
||||||
></div>
|
></div>
|
||||||
<div v-else class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
<div v-else class="col-12 col-md-4 col-lg-4 q-gutter-y-md">
|
||||||
<div class="gt-sm">
|
<div class="gt-sm">
|
||||||
<h3 class="q-my-none">{{SITE_TITLE}}</h3>
|
<h3 class="q-my-none" v-if="'{{SITE_TITLE}}' == 'LNbits'">
|
||||||
<h5 class="q-my-md">{{SITE_TAGLINE}}</h5>
|
{{SITE_TITLE}}
|
||||||
|
</h3>
|
||||||
|
<h5 class="q-my-md" v-if="'{{SITE_TITLE}}' == 'LNbits'">
|
||||||
|
{{SITE_TAGLINE}}
|
||||||
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
{% if lnurl and LNBITS_NEW_ACCOUNTS_ALLOWED and ("user-id-only" in
|
{% if lnurl and LNBITS_NEW_ACCOUNTS_ALLOWED and ("user-id-only" in
|
||||||
LNBITS_AUTH_METHODS)%}
|
LNBITS_AUTH_METHODS)%}
|
||||||
@@ -33,7 +37,7 @@
|
|||||||
{%else%} {% endif %}
|
{%else%} {% endif %}
|
||||||
|
|
||||||
<div class="row q-mt-md">
|
<div class="row q-mt-md">
|
||||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
<div class="col-12 col-md-10 col-lg-10 q-gutter-y-md">
|
||||||
<q-badge v-if="isAccessTokenExpired" color="primary" rounded>
|
<q-badge v-if="isAccessTokenExpired" color="primary" rounded>
|
||||||
<div class="text-h5">
|
<div class="text-h5">
|
||||||
<span v-text="$t('session_has_expired')"></span>
|
<span v-text="$t('session_has_expired')"></span>
|
||||||
@@ -259,6 +263,25 @@
|
|||||||
<div><span v-text="$t('signin_with_github')"></span></div>
|
<div><span v-text="$t('signin_with_github')"></span></div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
|
{%endif%} {% if "keycloak-auth" in LNBITS_AUTH_METHODS %}
|
||||||
|
<div class="col-12 full-width q-pa-sm">
|
||||||
|
<q-btn
|
||||||
|
href="/api/v1/auth/keycloak"
|
||||||
|
type="a"
|
||||||
|
outline
|
||||||
|
no-caps
|
||||||
|
color="grey"
|
||||||
|
rounded
|
||||||
|
class="full-width"
|
||||||
|
>
|
||||||
|
<q-avatar size="32px" class="q-mr-md">
|
||||||
|
<q-img
|
||||||
|
:src="'{{ static_url_for('static', 'images/keycloak-logo.png') }}'"
|
||||||
|
></q-img>
|
||||||
|
</q-avatar>
|
||||||
|
<div><span v-text="$t('signin_with_keycloak')"></span></div>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
{%endif%}
|
{%endif%}
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@@ -282,11 +305,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ads -->
|
|
||||||
<div
|
<div
|
||||||
class="col-12 col-md-3 col-lg-3 gt-sm"
|
v-if="'{{SITE_TITLE}}' != 'LNbits'"
|
||||||
v-if="'{{SITE_TITLE}}' == 'LNbits'"
|
class="col-12 col-md-5 col-lg-5 q-pt-xl"
|
||||||
>
|
>
|
||||||
|
<h3 class="q-my-none">{{SITE_TITLE}}</h3>
|
||||||
|
<h5 class="q-my-md">{{SITE_TAGLINE}}</h5>
|
||||||
|
<div v-html="formatDescription"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-md-3 col-lg-3 gt-sm" v-else>
|
||||||
<div class="row q-col-gutter-lg justify-center">
|
<div class="row q-col-gutter-lg justify-center">
|
||||||
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
||||||
<q-btn
|
<q-btn
|
||||||
@@ -465,27 +493,22 @@
|
|||||||
<div class="col q-pl-md"></div>
|
<div class="col q-pl-md"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = ADS.split(';') %}
|
|
||||||
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
|
||||||
<q-btn flat color="secondary" class="full-width q-mb-md"
|
|
||||||
>{{ AD_SPACE_TITLE }}</q-btn
|
|
||||||
>
|
|
||||||
|
|
||||||
<a href="{{ AD[0] }}" class="q-ma-md">
|
|
||||||
<img
|
|
||||||
v-if="($q.dark.isActive)"
|
|
||||||
src="{{ AD[1] }}"
|
|
||||||
style="max-width: 90%"
|
|
||||||
/>
|
|
||||||
<img v-else src="{{ AD[2] }}" style="max-width: 90%" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
{% endfor %} {% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD = ADS.split(';') %}
|
||||||
|
<div class="col-6 col-sm-4 col-md-8 q-gutter-y-sm">
|
||||||
|
<q-btn flat color="secondary" class="full-width q-mb-md"
|
||||||
|
>{{ AD_SPACE_TITLE }}</q-btn
|
||||||
|
>
|
||||||
|
|
||||||
|
<a href="{{ AD[0] }}" class="q-ma-md">
|
||||||
|
<img v-if="($q.dark.isActive)" src="{{ AD[1] }}" style="max-width: 90%" />
|
||||||
|
<img v-else src="{{ AD[2] }}" style="max-width: 90%" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endfor %} {% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row gt-sm q-mt-xl">
|
<div v-if="'{{SITE_TITLE}}' == 'LNbits'" class="row gt-sm q-mt-xl">
|
||||||
<div class="col-1"></div>
|
<div class="col-1"></div>
|
||||||
<div class="col-10 q-pl-xl">
|
<div class="col-10 q-pl-xl">
|
||||||
<span v-text="$t('lnbits_description')"></span>
|
<span v-text="$t('lnbits_description')"></span>
|
||||||
|
|||||||
@@ -922,8 +922,11 @@
|
|||||||
|
|
||||||
<q-dialog v-model="disclaimerDialog.show" position="top">
|
<q-dialog v-model="disclaimerDialog.show" position="top">
|
||||||
<q-card class="q-pa-lg">
|
<q-card class="q-pa-lg">
|
||||||
<h6 class="q-my-md text-primary">Warning</h6>
|
<h6
|
||||||
<p v-text="$t('disclaimer_dialog')"></p>
|
class="q-my-md text-primary"
|
||||||
|
v-text="$t('disclaimer_dialog_title')"
|
||||||
|
></h6>
|
||||||
|
<p class="whitespace-pre-line" v-text="$t('disclaimer_dialog')"></p>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
|
|||||||
@@ -17,9 +17,11 @@ from lnbits.core.services import (
|
|||||||
update_cached_settings,
|
update_cached_settings,
|
||||||
update_wallet_balance,
|
update_wallet_balance,
|
||||||
)
|
)
|
||||||
|
from lnbits.core.tasks import api_invoice_listeners
|
||||||
from lnbits.decorators import check_admin, check_super_user
|
from lnbits.decorators import check_admin, check_super_user
|
||||||
from lnbits.server import server_restart
|
from lnbits.server import server_restart
|
||||||
from lnbits.settings import AdminSettings, UpdateSettings, settings
|
from lnbits.settings import AdminSettings, UpdateSettings, settings
|
||||||
|
from lnbits.tasks import invoice_listeners
|
||||||
|
|
||||||
from .. import core_app_extra
|
from .. import core_app_extra
|
||||||
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
|
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
|
||||||
@@ -48,6 +50,19 @@ async def api_auditor():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.get(
|
||||||
|
"/admin/api/v1/monitor",
|
||||||
|
name="Monitor",
|
||||||
|
description="show the current listeners and other monitoring data",
|
||||||
|
dependencies=[Depends(check_admin)],
|
||||||
|
)
|
||||||
|
async def api_monitor():
|
||||||
|
return {
|
||||||
|
"invoice_listeners": list(invoice_listeners.keys()),
|
||||||
|
"api_invoice_listeners": list(api_invoice_listeners.keys()),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get("/admin/api/v1/settings/", response_model=Optional[AdminSettings])
|
@admin_router.get("/admin/api/v1/settings/", response_model=Optional[AdminSettings])
|
||||||
async def api_get_settings(
|
async def api_get_settings(
|
||||||
user: User = Depends(check_admin),
|
user: User = Depends(check_admin),
|
||||||
|
|||||||
@@ -824,8 +824,9 @@ async def api_install_extension(
|
|||||||
|
|
||||||
await add_installed_extension(ext_info)
|
await add_installed_extension(ext_info)
|
||||||
|
|
||||||
# call stop while the old routes are still active
|
if extension.is_upgrade_extension:
|
||||||
await stop_extension_background_work(data.ext_id, user.id, access_token)
|
# call stop while the old routes are still active
|
||||||
|
await stop_extension_background_work(data.ext_id, user.id, access_token)
|
||||||
|
|
||||||
if data.ext_id not in settings.lnbits_deactivated_extensions:
|
if data.ext_id not in settings.lnbits_deactivated_extensions:
|
||||||
settings.lnbits_deactivated_extensions += [data.ext_id]
|
settings.lnbits_deactivated_extensions += [data.ext_id]
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from typing import Optional
|
import importlib
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
from fastapi_sso.sso.base import OpenID
|
from fastapi_sso.sso.base import OpenID, SSOBase
|
||||||
from fastapi_sso.sso.github import GithubSSO
|
|
||||||
from fastapi_sso.sso.google import GoogleSSO
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from starlette.status import (
|
from starlette.status import (
|
||||||
HTTP_400_BAD_REQUEST,
|
HTTP_400_BAD_REQUEST,
|
||||||
@@ -94,72 +93,37 @@ async def login_usr(data: LoginUsr) -> JSONResponse:
|
|||||||
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.")
|
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.")
|
||||||
|
|
||||||
|
|
||||||
@auth_router.get("/api/v1/auth/google", description="Google SSO")
|
@auth_router.get("/api/v1/auth/{provider}", description="SSO Provider")
|
||||||
async def login_with_google(request: Request, user_id: Optional[str] = None):
|
async def login_with_sso_provider(
|
||||||
google_sso = _new_google_sso()
|
request: Request, provider: str, user_id: Optional[str] = None
|
||||||
if not google_sso:
|
):
|
||||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login by 'Google' not allowed.")
|
provider_sso = _new_sso(provider)
|
||||||
|
if not provider_sso:
|
||||||
google_sso.redirect_uri = str(request.base_url) + "api/v1/auth/google/token"
|
|
||||||
with google_sso:
|
|
||||||
state = encrypt_internal_message(user_id)
|
|
||||||
return await google_sso.get_login_redirect(state=state)
|
|
||||||
|
|
||||||
|
|
||||||
@auth_router.get("/api/v1/auth/github", description="Github SSO")
|
|
||||||
async def login_with_github(request: Request, user_id: Optional[str] = None):
|
|
||||||
github_sso = _new_github_sso()
|
|
||||||
if not github_sso:
|
|
||||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login by 'GitHub' not allowed.")
|
|
||||||
|
|
||||||
github_sso.redirect_uri = str(request.base_url) + "api/v1/auth/github/token"
|
|
||||||
with github_sso:
|
|
||||||
state = decrypt_internal_message(user_id)
|
|
||||||
return await github_sso.get_login_redirect(state=state)
|
|
||||||
|
|
||||||
|
|
||||||
@auth_router.get(
|
|
||||||
"/api/v1/auth/google/token", description="Handle Google OAuth callback"
|
|
||||||
)
|
|
||||||
async def handle_google_token(request: Request) -> RedirectResponse:
|
|
||||||
google_sso = _new_google_sso()
|
|
||||||
if not google_sso:
|
|
||||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login by 'Google' not allowed.")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with google_sso:
|
|
||||||
userinfo = await google_sso.verify_and_process(request)
|
|
||||||
assert userinfo is not None
|
|
||||||
user_id = decrypt_internal_message(google_sso.state)
|
|
||||||
request.session.pop("user", None)
|
|
||||||
return await _handle_sso_login(userinfo, user_id)
|
|
||||||
except HTTPException as e:
|
|
||||||
raise e
|
|
||||||
except ValueError as e:
|
|
||||||
raise HTTPException(HTTP_403_FORBIDDEN, str(e))
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(e)
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot authenticate user with Google Auth."
|
HTTP_401_UNAUTHORIZED, f"Login by '{provider}' not allowed."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
provider_sso.redirect_uri = str(request.base_url) + f"api/v1/auth/{provider}/token"
|
||||||
|
with provider_sso:
|
||||||
|
state = encrypt_internal_message(user_id)
|
||||||
|
return await provider_sso.get_login_redirect(state=state)
|
||||||
|
|
||||||
@auth_router.get(
|
|
||||||
"/api/v1/auth/github/token", description="Handle Github OAuth callback"
|
@auth_router.get("/api/v1/auth/{provider}/token", description="Handle OAuth callback")
|
||||||
)
|
async def handle_oauth_token(request: Request, provider: str) -> RedirectResponse:
|
||||||
async def handle_github_token(request: Request) -> RedirectResponse:
|
provider_sso = _new_sso(provider)
|
||||||
github_sso = _new_github_sso()
|
if not provider_sso:
|
||||||
if not github_sso:
|
raise HTTPException(
|
||||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login by 'GitHub' not allowed.")
|
HTTP_401_UNAUTHORIZED, f"Login by '{provider}' not allowed."
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with github_sso:
|
with provider_sso:
|
||||||
userinfo = await github_sso.verify_and_process(request)
|
userinfo = await provider_sso.verify_and_process(request)
|
||||||
assert userinfo is not None
|
assert userinfo is not None
|
||||||
user_id = decrypt_internal_message(github_sso.state)
|
user_id = decrypt_internal_message(provider_sso.state)
|
||||||
request.session.pop("user", None)
|
request.session.pop("user", None)
|
||||||
return await _handle_sso_login(userinfo, user_id)
|
return await _handle_sso_login(userinfo, user_id)
|
||||||
|
|
||||||
except HTTPException as e:
|
except HTTPException as e:
|
||||||
raise e
|
raise e
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -167,7 +131,8 @@ async def handle_github_token(request: Request) -> RedirectResponse:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(e)
|
logger.debug(e)
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTP_500_INTERNAL_SERVER_ERROR, "Cannot authenticate user with GitHub Auth."
|
HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
f"Cannot authenticate user with {provider} Auth.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -340,29 +305,44 @@ def _auth_redirect_response(path: str, email: str) -> RedirectResponse:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
def _new_google_sso() -> Optional[GoogleSSO]:
|
def _new_sso(provider: str) -> Optional[SSOBase]:
|
||||||
if not settings.is_auth_method_allowed(AuthMethods.google_auth):
|
try:
|
||||||
return None
|
if not settings.is_auth_method_allowed(AuthMethods(f"{provider}-auth")):
|
||||||
if not settings.is_google_auth_configured:
|
return None
|
||||||
logger.warning("Google Auth allowed but not configured.")
|
|
||||||
return None
|
client_id = getattr(settings, f"{provider}_client_id", None)
|
||||||
return GoogleSSO(
|
client_secret = getattr(settings, f"{provider}_client_secret", None)
|
||||||
settings.google_client_id,
|
discovery_url = getattr(settings, f"{provider}_discovery_url", None)
|
||||||
settings.google_client_secret,
|
|
||||||
None,
|
if not client_id or not client_secret:
|
||||||
allow_insecure_http=True,
|
logger.warning(f"{provider} auth allowed but not configured.")
|
||||||
)
|
return None
|
||||||
|
|
||||||
|
SSOProviderClass = _find_auth_provider_class(provider)
|
||||||
|
ssoProvider = SSOProviderClass(
|
||||||
|
client_id, client_secret, None, allow_insecure_http=True
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
discovery_url
|
||||||
|
and getattr(ssoProvider, "discovery_url", discovery_url) != discovery_url
|
||||||
|
):
|
||||||
|
ssoProvider.discovery_url = discovery_url
|
||||||
|
return ssoProvider
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _new_github_sso() -> Optional[GithubSSO]:
|
def _find_auth_provider_class(provider: str) -> Callable:
|
||||||
if not settings.is_auth_method_allowed(AuthMethods.github_auth):
|
sso_modules = ["lnbits.core.sso", "fastapi_sso.sso"]
|
||||||
return None
|
for module in sso_modules:
|
||||||
if not settings.is_github_auth_configured:
|
try:
|
||||||
logger.warning("Github Auth allowed but not configured.")
|
provider_module = importlib.import_module(f"{module}.{provider}")
|
||||||
return None
|
ProviderClass = getattr(provider_module, f"{provider.title()}SSO")
|
||||||
return GithubSSO(
|
if ProviderClass:
|
||||||
settings.github_client_id,
|
return ProviderClass
|
||||||
settings.github_client_secret,
|
except Exception:
|
||||||
None,
|
pass
|
||||||
allow_insecure_http=True,
|
|
||||||
)
|
raise ValueError(f"No SSO provider found for '{provider}'.")
|
||||||
|
|||||||
@@ -95,11 +95,10 @@ async def extensions_install(
|
|||||||
installed_exts: List["InstallableExtension"] = await get_installed_extensions()
|
installed_exts: List["InstallableExtension"] = await get_installed_extensions()
|
||||||
installed_exts_ids = [e.id for e in installed_exts]
|
installed_exts_ids = [e.id for e in installed_exts]
|
||||||
|
|
||||||
installable_exts: List[
|
installable_exts = await InstallableExtension.get_installable_extensions()
|
||||||
InstallableExtension
|
installable_exts_ids = [e.id for e in installable_exts]
|
||||||
] = await InstallableExtension.get_installable_extensions()
|
|
||||||
installable_exts += [
|
installable_exts += [
|
||||||
e for e in installed_exts if e.id not in installed_exts_ids
|
e for e in installed_exts if e.id not in installable_exts_ids
|
||||||
]
|
]
|
||||||
|
|
||||||
for e in installable_exts:
|
for e in installable_exts:
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ def template_renderer(additional_folders: Optional[List] = None) -> Jinja2Templa
|
|||||||
|
|
||||||
def get_current_extension_name() -> str:
|
def get_current_extension_name() -> str:
|
||||||
"""
|
"""
|
||||||
|
DEPRECATED: Use the repo name instead, will be removed in the future
|
||||||
|
before: `register_invoice_listener(invoice_queue, get_current_extension_name())`
|
||||||
|
after: `register_invoice_listener(invoice_queue, "my-extension")`
|
||||||
|
|
||||||
Returns the name of the extension that calls this method.
|
Returns the name of the extension that calls this method.
|
||||||
"""
|
"""
|
||||||
import inspect
|
import inspect
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class LndRestNode(Node):
|
|||||||
memo=invoice["memo"],
|
memo=invoice["memo"],
|
||||||
pending=invoice["state"] == "OPEN",
|
pending=invoice["state"] == "OPEN",
|
||||||
paid_at=invoice["settle_date"],
|
paid_at=invoice["settle_date"],
|
||||||
expiry=invoice["creation_date"] + invoice["expiry"],
|
expiry=int(invoice["creation_date"]) + int(invoice["expiry"]),
|
||||||
preimage=_decode_bytes(invoice["r_preimage"]),
|
preimage=_decode_bytes(invoice["r_preimage"]),
|
||||||
bolt11=invoice["payment_request"],
|
bolt11=invoice["payment_request"],
|
||||||
)
|
)
|
||||||
|
|||||||
+27
-7
@@ -68,6 +68,16 @@ class InstalledExtensionsSettings(LNbitsSettings):
|
|||||||
# list of redirects that extensions want to perform
|
# list of redirects that extensions want to perform
|
||||||
lnbits_extensions_redirects: List[Any] = Field(default=[])
|
lnbits_extensions_redirects: List[Any] = Field(default=[])
|
||||||
|
|
||||||
|
def extension_upgrade_path(self, ext_id: str) -> Optional[str]:
|
||||||
|
return next(
|
||||||
|
(e for e in self.lnbits_upgraded_extensions if e.endswith(f"/{ext_id}")),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
|
def extension_upgrade_hash(self, ext_id: str) -> Optional[str]:
|
||||||
|
path = settings.extension_upgrade_path(ext_id)
|
||||||
|
return path.split("/")[0] if path else None
|
||||||
|
|
||||||
|
|
||||||
class ThemesSettings(LNbitsSettings):
|
class ThemesSettings(LNbitsSettings):
|
||||||
lnbits_site_title: str = Field(default="LNbits")
|
lnbits_site_title: str = Field(default="LNbits")
|
||||||
@@ -116,6 +126,9 @@ class SecuritySettings(LNbitsSettings):
|
|||||||
lnbits_notifications: bool = Field(default=False)
|
lnbits_notifications: bool = Field(default=False)
|
||||||
lnbits_killswitch: bool = Field(default=False)
|
lnbits_killswitch: bool = Field(default=False)
|
||||||
lnbits_killswitch_interval: int = Field(default=60)
|
lnbits_killswitch_interval: int = Field(default=60)
|
||||||
|
lnbits_wallet_limit_max_balance: int = Field(default=0)
|
||||||
|
lnbits_wallet_limit_daily_max_withdraw: int = Field(default=0)
|
||||||
|
lnbits_wallet_limit_secs_between_trans: int = Field(default=0)
|
||||||
lnbits_watchdog: bool = Field(default=False)
|
lnbits_watchdog: bool = Field(default=False)
|
||||||
lnbits_watchdog_interval: int = Field(default=60)
|
lnbits_watchdog_interval: int = Field(default=60)
|
||||||
lnbits_watchdog_delta: int = Field(default=1_000_000)
|
lnbits_watchdog_delta: int = Field(default=1_000_000)
|
||||||
@@ -125,6 +138,13 @@ class SecuritySettings(LNbitsSettings):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_wallet_max_balance_exceeded(self, amount):
|
||||||
|
return (
|
||||||
|
self.lnbits_wallet_limit_max_balance
|
||||||
|
and self.lnbits_wallet_limit_max_balance > 0
|
||||||
|
and amount > self.lnbits_wallet_limit_max_balance
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FakeWalletFundingSource(LNbitsSettings):
|
class FakeWalletFundingSource(LNbitsSettings):
|
||||||
fake_wallet_secret: str = Field(default="ToTheMoon1")
|
fake_wallet_secret: str = Field(default="ToTheMoon1")
|
||||||
@@ -252,6 +272,7 @@ class AuthMethods(Enum):
|
|||||||
username_and_password = "username-password"
|
username_and_password = "username-password"
|
||||||
google_auth = "google-auth"
|
google_auth = "google-auth"
|
||||||
github_auth = "github-auth"
|
github_auth = "github-auth"
|
||||||
|
keycloak_auth = "keycloak-auth"
|
||||||
|
|
||||||
|
|
||||||
class AuthSettings(LNbitsSettings):
|
class AuthSettings(LNbitsSettings):
|
||||||
@@ -272,18 +293,16 @@ class GoogleAuthSettings(LNbitsSettings):
|
|||||||
google_client_id: str = Field(default="")
|
google_client_id: str = Field(default="")
|
||||||
google_client_secret: str = Field(default="")
|
google_client_secret: str = Field(default="")
|
||||||
|
|
||||||
@property
|
|
||||||
def is_google_auth_configured(self):
|
|
||||||
return self.google_client_id != "" and self.google_client_secret != ""
|
|
||||||
|
|
||||||
|
|
||||||
class GitHubAuthSettings(LNbitsSettings):
|
class GitHubAuthSettings(LNbitsSettings):
|
||||||
github_client_id: str = Field(default="")
|
github_client_id: str = Field(default="")
|
||||||
github_client_secret: str = Field(default="")
|
github_client_secret: str = Field(default="")
|
||||||
|
|
||||||
@property
|
|
||||||
def is_github_auth_configured(self):
|
class KeycloakAuthSettings(LNbitsSettings):
|
||||||
return self.github_client_id != "" and self.github_client_secret != ""
|
keycloak_discovery_url: str = Field(default="")
|
||||||
|
keycloak_client_id: str = Field(default="")
|
||||||
|
keycloak_client_secret: str = Field(default="")
|
||||||
|
|
||||||
|
|
||||||
class EditableSettings(
|
class EditableSettings(
|
||||||
@@ -299,6 +318,7 @@ class EditableSettings(
|
|||||||
AuthSettings,
|
AuthSettings,
|
||||||
GoogleAuthSettings,
|
GoogleAuthSettings,
|
||||||
GitHubAuthSettings,
|
GitHubAuthSettings,
|
||||||
|
KeycloakAuthSettings,
|
||||||
):
|
):
|
||||||
@validator(
|
@validator(
|
||||||
"lnbits_admin_users",
|
"lnbits_admin_users",
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+10
-10
File diff suppressed because one or more lines are too long
@@ -550,3 +550,7 @@ video {
|
|||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.whitespace-pre-line {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.br = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Taxa de serviço cobrada pelo administrador do servidor LNbits por transação de saída',
|
'Taxa de serviço cobrada pelo administrador do servidor LNbits por transação de saída',
|
||||||
toggle_darkmode: 'Alternar modo escuro',
|
toggle_darkmode: 'Alternar modo escuro',
|
||||||
|
payment_reactions: 'Reações de Pagamento',
|
||||||
view_swagger_docs: 'Ver a documentação da API do LNbits Swagger',
|
view_swagger_docs: 'Ver a documentação da API do LNbits Swagger',
|
||||||
api_docs: 'Documentação da API',
|
api_docs: 'Documentação da API',
|
||||||
api_keys_api_docs: 'Chaves de API e documentação da API',
|
api_keys_api_docs: 'URL do Node, chaves da API e documentação da API',
|
||||||
lnbits_version: 'Versão do LNbits',
|
lnbits_version: 'Versão do LNbits',
|
||||||
runs_on: 'Executa em',
|
runs_on: 'Executa em',
|
||||||
credit_hint: 'Pressione Enter para creditar a conta',
|
credit_hint: 'Pressione Enter para creditar a conta',
|
||||||
@@ -190,6 +191,12 @@ window.localisation.br = {
|
|||||||
allow_access_hint: 'Permitir acesso por IP (substituirá os IPs bloqueados)',
|
allow_access_hint: 'Permitir acesso por IP (substituirá os IPs bloqueados)',
|
||||||
enter_ip: 'Digite o IP e pressione enter',
|
enter_ip: 'Digite o IP e pressione enter',
|
||||||
rate_limiter: 'Limitador de Taxa',
|
rate_limiter: 'Limitador de Taxa',
|
||||||
|
wallet_limiter: 'Limitador de Carteira',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Retirada máxima diária da carteira em sats (0 para desativar)',
|
||||||
|
wallet_max_ballance: 'Saldo máximo da carteira em sats (0 para desativar)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minutos e segundos entre transações por carteira (0 para desativar)',
|
||||||
number_of_requests: 'Número de solicitações',
|
number_of_requests: 'Número de solicitações',
|
||||||
time_unit: 'Unidade de tempo',
|
time_unit: 'Unidade de tempo',
|
||||||
minute: 'minuto',
|
minute: 'minuto',
|
||||||
@@ -208,6 +215,7 @@ window.localisation.br = {
|
|||||||
account_settings: 'Configurações da Conta',
|
account_settings: 'Configurações da Conta',
|
||||||
signin_with_google: 'Entrar com o Google',
|
signin_with_google: 'Entrar com o Google',
|
||||||
signin_with_github: 'Entrar com GitHub',
|
signin_with_github: 'Entrar com GitHub',
|
||||||
|
signin_with_keycloak: 'Entrar com Keycloak',
|
||||||
username_or_email: 'Nome de usuário ou E-mail',
|
username_or_email: 'Nome de usuário ou E-mail',
|
||||||
password: 'Senha',
|
password: 'Senha',
|
||||||
password_config: 'Configuração de Senha',
|
password_config: 'Configuração de Senha',
|
||||||
@@ -230,5 +238,8 @@ window.localisation.br = {
|
|||||||
auth_provider: 'Provedor de Autenticação',
|
auth_provider: 'Provedor de Autenticação',
|
||||||
my_account: 'Minha Conta',
|
my_account: 'Minha Conta',
|
||||||
back: 'Voltar',
|
back: 'Voltar',
|
||||||
logout: 'Sair'
|
logout: 'Sair',
|
||||||
|
look_and_feel: 'Aparência',
|
||||||
|
language: 'Idioma',
|
||||||
|
color_scheme: 'Esquema de Cores'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,9 +57,10 @@ window.localisation.cn = {
|
|||||||
service_fee_max: '服务费:%{amount}% 每笔交易(最高 %{max} sats)',
|
service_fee_max: '服务费:%{amount}% 每笔交易(最高 %{max} sats)',
|
||||||
service_fee_tooltip: 'LNbits服务器管理员每笔外发交易收取的服务费',
|
service_fee_tooltip: 'LNbits服务器管理员每笔外发交易收取的服务费',
|
||||||
toggle_darkmode: '切换暗黑模式',
|
toggle_darkmode: '切换暗黑模式',
|
||||||
|
payment_reactions: '支付反应',
|
||||||
view_swagger_docs: '查看 LNbits Swagger API 文档',
|
view_swagger_docs: '查看 LNbits Swagger API 文档',
|
||||||
api_docs: 'API文档',
|
api_docs: 'API文档',
|
||||||
api_keys_api_docs: 'API密钥和API文档',
|
api_keys_api_docs: '节点URL、API密钥和API文档',
|
||||||
lnbits_version: 'LNbits版本',
|
lnbits_version: 'LNbits版本',
|
||||||
runs_on: '可运行在',
|
runs_on: '可运行在',
|
||||||
credit_hint: '按 Enter 键充值账户',
|
credit_hint: '按 Enter 键充值账户',
|
||||||
@@ -179,6 +180,11 @@ window.localisation.cn = {
|
|||||||
allow_access_hint: '允许通过IP访问(将覆盖被屏蔽的IP)',
|
allow_access_hint: '允许通过IP访问(将覆盖被屏蔽的IP)',
|
||||||
enter_ip: '输入IP地址并按回车键',
|
enter_ip: '输入IP地址并按回车键',
|
||||||
rate_limiter: '速率限制器',
|
rate_limiter: '速率限制器',
|
||||||
|
wallet_limiter: '钱包限制器',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'每日钱包最大提现额度(单位:sats)(设为0则禁用)',
|
||||||
|
wallet_max_ballance: '钱包最大余额(以sats计)(设为0则禁用)',
|
||||||
|
wallet_limit_secs_between_trans: '每个钱包交易间最少秒数(设为0则禁用)',
|
||||||
number_of_requests: '请求次数',
|
number_of_requests: '请求次数',
|
||||||
time_unit: '时间单位',
|
time_unit: '时间单位',
|
||||||
minute: '分钟',
|
minute: '分钟',
|
||||||
@@ -196,7 +202,8 @@ window.localisation.cn = {
|
|||||||
create_account: '创建账户',
|
create_account: '创建账户',
|
||||||
account_settings: '账户设置',
|
account_settings: '账户设置',
|
||||||
signin_with_google: '使用谷歌账号登录',
|
signin_with_google: '使用谷歌账号登录',
|
||||||
signin_with_github: '使用 GitHub 登录',
|
signin_with_github: '使用GitHub登录',
|
||||||
|
signin_with_keycloak: '使用Keycloak登录',
|
||||||
username_or_email: '用户名或电子邮箱',
|
username_or_email: '用户名或电子邮箱',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
password_config: '密码配置',
|
password_config: '密码配置',
|
||||||
@@ -219,5 +226,8 @@ window.localisation.cn = {
|
|||||||
auth_provider: '认证提供者',
|
auth_provider: '认证提供者',
|
||||||
my_account: '我的账户',
|
my_account: '我的账户',
|
||||||
back: '返回',
|
back: '返回',
|
||||||
logout: '注销'
|
logout: '注销',
|
||||||
|
look_and_feel: '外观和感觉',
|
||||||
|
language: '语言',
|
||||||
|
color_scheme: '配色方案'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.cs = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Servisní poplatek účtovaný správcem LNbits serveru za odchozí transakci',
|
'Servisní poplatek účtovaný správcem LNbits serveru za odchozí transakci',
|
||||||
toggle_darkmode: 'Přepnout tmavý režim',
|
toggle_darkmode: 'Přepnout tmavý režim',
|
||||||
|
payment_reactions: 'Reakce na platby',
|
||||||
view_swagger_docs: 'Zobrazit LNbits Swagger API dokumentaci',
|
view_swagger_docs: 'Zobrazit LNbits Swagger API dokumentaci',
|
||||||
api_docs: 'API dokumentace',
|
api_docs: 'API dokumentace',
|
||||||
api_keys_api_docs: 'API klíče a API dokumentace',
|
api_keys_api_docs: 'Adresa uzlu, API klíče a API dokumentace',
|
||||||
lnbits_version: 'Verze LNbits',
|
lnbits_version: 'Verze LNbits',
|
||||||
runs_on: 'Běží na',
|
runs_on: 'Běží na',
|
||||||
credit_hint: 'Stiskněte Enter pro připsání na účet',
|
credit_hint: 'Stiskněte Enter pro připsání na účet',
|
||||||
@@ -187,6 +188,12 @@ window.localisation.cs = {
|
|||||||
allow_access_hint: 'Povolit přístup podle IP (přepíše blokované IP)',
|
allow_access_hint: 'Povolit přístup podle IP (přepíše blokované IP)',
|
||||||
enter_ip: 'Zadejte IP a stiskněte enter',
|
enter_ip: 'Zadejte IP a stiskněte enter',
|
||||||
rate_limiter: 'Omezovač počtu požadavků',
|
rate_limiter: 'Omezovač počtu požadavků',
|
||||||
|
wallet_limiter: 'Omezení peněženky',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maximální denní limit pro výběr z peněženky v sats (0 pro deaktivaci)',
|
||||||
|
wallet_max_ballance: 'Maximální zůstatek v peněžence v sats (0 pro zakázání)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minimální počet sekund mezi transakcemi na peněženku (0 pro vypnutí)',
|
||||||
number_of_requests: 'Počet požadavků',
|
number_of_requests: 'Počet požadavků',
|
||||||
time_unit: 'Časová jednotka',
|
time_unit: 'Časová jednotka',
|
||||||
minute: 'minuta',
|
minute: 'minuta',
|
||||||
@@ -205,6 +212,7 @@ window.localisation.cs = {
|
|||||||
account_settings: 'Nastavení účtu',
|
account_settings: 'Nastavení účtu',
|
||||||
signin_with_google: 'Přihlásit se přes Google',
|
signin_with_google: 'Přihlásit se přes Google',
|
||||||
signin_with_github: 'Přihlásit se přes GitHub',
|
signin_with_github: 'Přihlásit se přes GitHub',
|
||||||
|
signin_with_keycloak: 'Přihlásit se přes Keycloak',
|
||||||
username_or_email: 'Uživatelské jméno nebo Email',
|
username_or_email: 'Uživatelské jméno nebo Email',
|
||||||
password: 'Heslo',
|
password: 'Heslo',
|
||||||
password_config: 'Konfigurace hesla',
|
password_config: 'Konfigurace hesla',
|
||||||
@@ -227,5 +235,8 @@ window.localisation.cs = {
|
|||||||
auth_provider: 'Poskytovatel ověření',
|
auth_provider: 'Poskytovatel ověření',
|
||||||
my_account: 'Můj účet',
|
my_account: 'Můj účet',
|
||||||
back: 'Zpět',
|
back: 'Zpět',
|
||||||
logout: 'Odhlásit se'
|
logout: 'Odhlásit se',
|
||||||
|
look_and_feel: 'Vzhled a chování',
|
||||||
|
language: 'Jazyk',
|
||||||
|
color_scheme: 'Barevné schéma'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ window.localisation.de = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Bearbeitungsgebühr, die vom LNbits Server-Administrator pro ausgehender Transaktion berechnet wird',
|
'Bearbeitungsgebühr, die vom LNbits Server-Administrator pro ausgehender Transaktion berechnet wird',
|
||||||
toggle_darkmode: 'Auf Dark Mode umschalten',
|
toggle_darkmode: 'Auf Dark Mode umschalten',
|
||||||
|
payment_reactions: 'Zahlungsreaktionen',
|
||||||
view_swagger_docs: 'LNbits Swagger API-Dokumentation',
|
view_swagger_docs: 'LNbits Swagger API-Dokumentation',
|
||||||
api_docs: 'API-Dokumentation',
|
api_docs: 'API-Dokumentation',
|
||||||
api_keys_api_docs: 'API-Schlüssel und API-Dokumentation',
|
api_keys_api_docs: 'Knoten-URL, API-Schlüssel und API-Dokumentation',
|
||||||
lnbits_version: 'LNbits-Version',
|
lnbits_version: 'LNbits-Version',
|
||||||
runs_on: 'Läuft auf',
|
runs_on: 'Läuft auf',
|
||||||
credit_hint: 'Klicke Enter, um das Konto zu belasten',
|
credit_hint: 'Klicke Enter, um das Konto zu belasten',
|
||||||
@@ -192,6 +193,13 @@ window.localisation.de = {
|
|||||||
allow_access_hint: 'Zugriff durch IP erlauben (überschreibt blockierte IPs)',
|
allow_access_hint: 'Zugriff durch IP erlauben (überschreibt blockierte IPs)',
|
||||||
enter_ip: 'Geben Sie die IP ein und drücken Sie die Eingabetaste',
|
enter_ip: 'Geben Sie die IP ein und drücken Sie die Eingabetaste',
|
||||||
rate_limiter: 'Ratenbegrenzer',
|
rate_limiter: 'Ratenbegrenzer',
|
||||||
|
wallet_limiter: 'Geldbeutel-Limiter',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maximales tägliches Wallet-Auszahlungslimit in Sats (0 zum Deaktivieren)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Maximales Guthaben der Wallet in Sats (0 zum Deaktivieren)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Mindestsekunden zwischen Transaktionen pro Wallet (0 zum Deaktivieren)',
|
||||||
number_of_requests: 'Anzahl der Anfragen',
|
number_of_requests: 'Anzahl der Anfragen',
|
||||||
time_unit: 'Zeiteinheit',
|
time_unit: 'Zeiteinheit',
|
||||||
minute: 'Minute',
|
minute: 'Minute',
|
||||||
@@ -211,6 +219,7 @@ window.localisation.de = {
|
|||||||
account_settings: 'Kontoeinstellungen',
|
account_settings: 'Kontoeinstellungen',
|
||||||
signin_with_google: 'Mit Google anmelden',
|
signin_with_google: 'Mit Google anmelden',
|
||||||
signin_with_github: 'Anmelden mit GitHub',
|
signin_with_github: 'Anmelden mit GitHub',
|
||||||
|
signin_with_keycloak: 'Mit Keycloak anmelden',
|
||||||
username_or_email: 'Benutzername oder E-Mail',
|
username_or_email: 'Benutzername oder E-Mail',
|
||||||
password: 'Passwort',
|
password: 'Passwort',
|
||||||
password_config: 'Passwortkonfiguration',
|
password_config: 'Passwortkonfiguration',
|
||||||
@@ -233,5 +242,8 @@ window.localisation.de = {
|
|||||||
auth_provider: 'Anbieter für Authentifizierung',
|
auth_provider: 'Anbieter für Authentifizierung',
|
||||||
my_account: 'Mein Konto',
|
my_account: 'Mein Konto',
|
||||||
back: 'Zurück',
|
back: 'Zurück',
|
||||||
logout: 'Abmelden'
|
logout: 'Abmelden',
|
||||||
|
look_and_feel: 'Aussehen und Verhalten',
|
||||||
|
language: 'Sprache',
|
||||||
|
color_scheme: 'Farbschema'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,9 +59,10 @@ window.localisation.en = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Service fee charged by the LNbits server admin per outgoing transaction',
|
'Service fee charged by the LNbits server admin per outgoing transaction',
|
||||||
toggle_darkmode: 'Toggle Dark Mode',
|
toggle_darkmode: 'Toggle Dark Mode',
|
||||||
|
payment_reactions: 'Payment Reactions',
|
||||||
view_swagger_docs: 'View LNbits Swagger API docs',
|
view_swagger_docs: 'View LNbits Swagger API docs',
|
||||||
api_docs: 'API docs',
|
api_docs: 'API docs',
|
||||||
api_keys_api_docs: 'API keys and API docs',
|
api_keys_api_docs: 'Node URL, API keys and API docs',
|
||||||
lnbits_version: 'LNbits version',
|
lnbits_version: 'LNbits version',
|
||||||
runs_on: 'Runs on',
|
runs_on: 'Runs on',
|
||||||
credit_hint: 'Press Enter to credit account',
|
credit_hint: 'Press Enter to credit account',
|
||||||
@@ -96,8 +97,12 @@ window.localisation.en = {
|
|||||||
'This is an LNURL-withdraw QR code for slurping everything from this wallet. Do not share with anyone. It is compatible with balanceCheck and balanceNotify so your wallet may keep pulling the funds continuously from here after the first withdraw.',
|
'This is an LNURL-withdraw QR code for slurping everything from this wallet. Do not share with anyone. It is compatible with balanceCheck and balanceNotify so your wallet may keep pulling the funds continuously from here after the first withdraw.',
|
||||||
i_understand: 'I understand',
|
i_understand: 'I understand',
|
||||||
copy_wallet_url: 'Copy wallet URL',
|
copy_wallet_url: 'Copy wallet URL',
|
||||||
disclaimer_dialog:
|
disclaimer_dialog_title: 'Important!',
|
||||||
'To ensure continuous access to your wallets, please remember to securely store your login credentials! Please visit the "My Account" page. This service is in BETA, and we hold no responsibility for people losing access to funds.',
|
disclaimer_dialog: `You *must* save your login credentials to be able to access your wallet again.If you lose them, you will lose access to your wallet and funds.
|
||||||
|
|
||||||
|
Find your login credentials on your account settings page.
|
||||||
|
|
||||||
|
This service is in BETA. LNbits holds no responsibility for loss of access to funds.`,
|
||||||
no_transactions: 'No transactions made yet',
|
no_transactions: 'No transactions made yet',
|
||||||
manage: 'Manage',
|
manage: 'Manage',
|
||||||
extensions: 'Extensions',
|
extensions: 'Extensions',
|
||||||
@@ -184,6 +189,12 @@ window.localisation.en = {
|
|||||||
allow_access_hint: 'Allow access by IP (will override blocked IPs)',
|
allow_access_hint: 'Allow access by IP (will override blocked IPs)',
|
||||||
enter_ip: 'Enter IP and hit enter',
|
enter_ip: 'Enter IP and hit enter',
|
||||||
rate_limiter: 'Rate Limiter',
|
rate_limiter: 'Rate Limiter',
|
||||||
|
wallet_limiter: 'Wallet Limiter',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Max daily wallet withdrawal in sats (0 to disable)',
|
||||||
|
wallet_max_ballance: 'Wallet max balance in sats (0 to disable)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Min secs between transactions per wallet (0 to disable)',
|
||||||
number_of_requests: 'Number of requests',
|
number_of_requests: 'Number of requests',
|
||||||
time_unit: 'Time unit',
|
time_unit: 'Time unit',
|
||||||
minute: 'minute',
|
minute: 'minute',
|
||||||
@@ -202,6 +213,7 @@ window.localisation.en = {
|
|||||||
account_settings: 'Account Settings',
|
account_settings: 'Account Settings',
|
||||||
signin_with_google: 'Sign in with Google',
|
signin_with_google: 'Sign in with Google',
|
||||||
signin_with_github: 'Sign in with GitHub',
|
signin_with_github: 'Sign in with GitHub',
|
||||||
|
signin_with_keycloak: 'Sign in with Keycloak',
|
||||||
username_or_email: 'Username or Email',
|
username_or_email: 'Username or Email',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
password_config: 'Password Config',
|
password_config: 'Password Config',
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.es = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Comisión de servicio cobrada por el administrador del servidor LNbits por cada transacción saliente',
|
'Comisión de servicio cobrada por el administrador del servidor LNbits por cada transacción saliente',
|
||||||
toggle_darkmode: 'Cambiar modo oscuro',
|
toggle_darkmode: 'Cambiar modo oscuro',
|
||||||
|
payment_reactions: 'Reacciones de Pago',
|
||||||
view_swagger_docs: 'Ver documentación de API de LNbits Swagger',
|
view_swagger_docs: 'Ver documentación de API de LNbits Swagger',
|
||||||
api_docs: 'Documentación de API',
|
api_docs: 'Documentación de API',
|
||||||
api_keys_api_docs: 'Claves de API y documentación de API',
|
api_keys_api_docs: 'URL del nodo, claves de API y documentación de API',
|
||||||
lnbits_version: 'Versión de LNbits',
|
lnbits_version: 'Versión de LNbits',
|
||||||
runs_on: 'Corre en',
|
runs_on: 'Corre en',
|
||||||
credit_hint: 'Presione Enter para cargar la cuenta',
|
credit_hint: 'Presione Enter para cargar la cuenta',
|
||||||
@@ -190,6 +191,13 @@ window.localisation.es = {
|
|||||||
allow_access_hint: 'Permitir acceso por IP (anulará las IPs bloqueadas)',
|
allow_access_hint: 'Permitir acceso por IP (anulará las IPs bloqueadas)',
|
||||||
enter_ip: 'Ingrese la IP y presione enter',
|
enter_ip: 'Ingrese la IP y presione enter',
|
||||||
rate_limiter: 'Limitador de tasa',
|
rate_limiter: 'Limitador de tasa',
|
||||||
|
wallet_limiter: 'Limitador de Cartera',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Límite diario de retiro de la cartera en sats (0 para deshabilitar)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Saldo máximo de la billetera en sats (0 para desactivar)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Mín. segs entre transacciones por cartera (0 para desactivar)',
|
||||||
number_of_requests: 'Número de solicitudes',
|
number_of_requests: 'Número de solicitudes',
|
||||||
time_unit: 'Unidad de tiempo',
|
time_unit: 'Unidad de tiempo',
|
||||||
minute: 'minuto',
|
minute: 'minuto',
|
||||||
@@ -209,6 +217,7 @@ window.localisation.es = {
|
|||||||
account_settings: 'Configuración de la cuenta',
|
account_settings: 'Configuración de la cuenta',
|
||||||
signin_with_google: 'Inicia sesión con Google',
|
signin_with_google: 'Inicia sesión con Google',
|
||||||
signin_with_github: 'Inicia sesión con GitHub',
|
signin_with_github: 'Inicia sesión con GitHub',
|
||||||
|
signin_with_keycloak: 'Iniciar sesión con Keycloak',
|
||||||
username_or_email: 'Nombre de usuario o correo electrónico',
|
username_or_email: 'Nombre de usuario o correo electrónico',
|
||||||
password: 'Contraseña',
|
password: 'Contraseña',
|
||||||
password_config: 'Configuración de Contraseña',
|
password_config: 'Configuración de Contraseña',
|
||||||
@@ -231,5 +240,8 @@ window.localisation.es = {
|
|||||||
auth_provider: 'Proveedor de Autenticación',
|
auth_provider: 'Proveedor de Autenticación',
|
||||||
my_account: 'Mi cuenta',
|
my_account: 'Mi cuenta',
|
||||||
back: 'Atrás',
|
back: 'Atrás',
|
||||||
logout: 'Cerrar sesión'
|
logout: 'Cerrar sesión',
|
||||||
|
look_and_feel: 'Apariencia',
|
||||||
|
language: 'Idioma',
|
||||||
|
color_scheme: 'Esquema de colores'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ window.localisation.fi = {
|
|||||||
paste_invoice_label:
|
paste_invoice_label:
|
||||||
'Liitä lasku, maksupyyntö, lnurl-koodi tai Lightning Address *',
|
'Liitä lasku, maksupyyntö, lnurl-koodi tai Lightning Address *',
|
||||||
lnbits_description:
|
lnbits_description:
|
||||||
'Kevyt ja helppokäyttöinen LNbits soveltuu minkä tahansa salamaverkon rahoituslähteeksi. Se tukee rahoituslähteinä LND, Core Lightning, OpenNode, Alby, LNPay ja jopa itseään! Voi käyttää itsenäisesti sitä ja helposti tarjota säilytyspalveluita. Pystyt luomaan sillä salamaverkko lompakoita ja niiden määrää ei ole rajoitettu. Jokaiselle lompakolle saat iksilölliset AP avaimet. Varojen osittaminen tekee siitä erittäin kätevän varojen hallinnassa sekä myös ohjelmistokehityksen työkaluja. Laajennukset lisäävät LNbits:in toiminnallisuuksia. Näinpä voit helposti testailla useita erilaisia ja viimeisimpiä salamaverkon-teknologioita. Laajennuksien kehittämisen olemme pyrkineet tekemään hmahdollisimman helpoksi, ilmaisena OpenSource-projektina. Kannustamme kaikkia kehittämään ja jakelemaan omia laajennuksia!',
|
'Kevyt ja helppokäyttöinen LNbits voi käyttää rahoituslähteinään erilaisia palveluita, kuten LND, Core Lightning, OpenNode, Alby, LNPay ja jopa itseään! Voit käyttää sitä itsenäisesti ja helposti tarjota erilaisia Lightning-palveluita. Pystyt luomaan sillä salamaverkkolompakoita eikä niiden määrää ole rajoitettu. Jokaiselle lompakolle saat yksilölliset API-avaimet. Varojen osittaminen tekee siitä erittäin kätevän varojen hallinnassa sekä myös ohjelmistokehityksen työkalun. Laajennukset lisäävät LNbits:in toiminnallisuuksia. Näinpä voit helposti testailla useita erilaisia ja viimeisimpiä salamaverkon teknologioita. Laajennuksien kehittämisen olemme pyrkineet tekemään mahdollisimman helpoksi pitämällä LNbits:in ilmaisena OpenSource-projektina. Kannustamme kaikkia kehittämään ja jakelemaan omia laajennuksia!',
|
||||||
export_to_phone: 'Käytä puhelimessa lukemalla QR-koodi',
|
export_to_phone: 'Käytä puhelimessa lukemalla QR-koodi',
|
||||||
export_to_phone_desc:
|
export_to_phone_desc:
|
||||||
'Tämä QR-koodi sisältää URL-osoitteen, jolla saa lompakkoosi täydet valtuudet. Voi lukea sen puhelimellasi ja avata sillä lompakkosi. Voit myös lisätä lompakkosi selaimella käytettäväksi PWA-sovellukseksi puhelimen aloitusruudulle. ',
|
'Tämä QR-koodi sisältää URL-osoitteen, jolla saa lompakkoosi täydet valtuudet. Voi lukea sen puhelimellasi ja avata sillä lompakkosi. Voit myös lisätä lompakkosi selaimella käytettäväksi PWA-sovellukseksi puhelimen aloitusruudulle. ',
|
||||||
@@ -64,9 +64,10 @@ window.localisation.fi = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'LNBits palvelimen ylläpitäjä veloittaa lähtevästä maksusta palvelumaksun.',
|
'LNBits palvelimen ylläpitäjä veloittaa lähtevästä maksusta palvelumaksun.',
|
||||||
toggle_darkmode: 'Tumma näkymä',
|
toggle_darkmode: 'Tumma näkymä',
|
||||||
|
toggle_reactions: 'Käytä tapahtuma efektejä',
|
||||||
view_swagger_docs: 'Näytä LNbits Swagger API-dokumentit',
|
view_swagger_docs: 'Näytä LNbits Swagger API-dokumentit',
|
||||||
api_docs: 'API-dokumentaatio',
|
api_docs: 'API-dokumentaatio',
|
||||||
api_keys_api_docs: 'API-avaimet ja -dokumentaatio',
|
api_keys_api_docs: 'Solmun URL, API-avaimet ja -dokumentaatio',
|
||||||
lnbits_version: 'LNbits versio',
|
lnbits_version: 'LNbits versio',
|
||||||
runs_on: 'Mukana menossa',
|
runs_on: 'Mukana menossa',
|
||||||
credit_hint: 'Hyväksy painamalla Enter',
|
credit_hint: 'Hyväksy painamalla Enter',
|
||||||
@@ -104,9 +105,9 @@ window.localisation.fi = {
|
|||||||
disclaimer_dialog:
|
disclaimer_dialog:
|
||||||
'Muistathan tallettaa kirjautumistietosi turvallisesta ja helposti saataville, jotta pääset jatkossakin kirjautumaan lompakkoosi! Tutustu myös Tilin asetukset -sivuun. Tämä palvelu on kokeiluvaiheessa (eli BETA), ja niinpä kukaan ei ota mitään vastuuta varojen säilymisestä tai niiden käytettävyyden takaamisesta.',
|
'Muistathan tallettaa kirjautumistietosi turvallisesta ja helposti saataville, jotta pääset jatkossakin kirjautumaan lompakkoosi! Tutustu myös Tilin asetukset -sivuun. Tämä palvelu on kokeiluvaiheessa (eli BETA), ja niinpä kukaan ei ota mitään vastuuta varojen säilymisestä tai niiden käytettävyyden takaamisesta.',
|
||||||
no_transactions: 'Lompakossa ei ole yhtään tapahtumaa',
|
no_transactions: 'Lompakossa ei ole yhtään tapahtumaa',
|
||||||
manage: 'Hallinnoi',
|
manage: 'Hallinnointi',
|
||||||
extensions: 'Laajennukset',
|
extensions: 'Laajennukset',
|
||||||
no_extensions: 'Yhtään laajennusta ei ole asennettuna :(',
|
no_extensions: 'Laajennuksia ei ole asennettu :(',
|
||||||
created: 'Luotu',
|
created: 'Luotu',
|
||||||
search_extensions: 'Etsi laajennuksia',
|
search_extensions: 'Etsi laajennuksia',
|
||||||
warning: 'Varoitus',
|
warning: 'Varoitus',
|
||||||
@@ -190,6 +191,12 @@ window.localisation.fi = {
|
|||||||
allow_access_hint: 'Salli pääsy IP-osoitteen perusteella (ohittaa estot)',
|
allow_access_hint: 'Salli pääsy IP-osoitteen perusteella (ohittaa estot)',
|
||||||
enter_ip: 'Anna IP ja paina +',
|
enter_ip: 'Anna IP ja paina +',
|
||||||
rate_limiter: 'Toiston rajoitin',
|
rate_limiter: 'Toiston rajoitin',
|
||||||
|
wallet_limiter: 'Lompakon Rajoitin',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maksimi päivittäinen lompakon nosto sateissa (0 poistaa käytöstä)',
|
||||||
|
wallet_max_ballance: 'Lompakon maksimisaldo satosheina (0 poistaa käytöstä)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Min sekuntia transaktioiden välillä lompakkoa kohden (0 poistaa käytöstä)',
|
||||||
number_of_requests: 'Pyyntöjen lukumäärä',
|
number_of_requests: 'Pyyntöjen lukumäärä',
|
||||||
time_unit: 'aikayksikkö',
|
time_unit: 'aikayksikkö',
|
||||||
minute: 'minuutti',
|
minute: 'minuutti',
|
||||||
@@ -208,6 +215,7 @@ window.localisation.fi = {
|
|||||||
account_settings: 'Tilin asetukset',
|
account_settings: 'Tilin asetukset',
|
||||||
signin_with_google: 'Kirjaudu Google-tunnuksella',
|
signin_with_google: 'Kirjaudu Google-tunnuksella',
|
||||||
signin_with_github: 'Kirjaudu GitHub-tunnuksella',
|
signin_with_github: 'Kirjaudu GitHub-tunnuksella',
|
||||||
|
signin_with_keycloak: 'Kirjaudu Keycloak-tunnuksella',
|
||||||
username_or_email: 'Käyttäjänimi tai sähköposti',
|
username_or_email: 'Käyttäjänimi tai sähköposti',
|
||||||
password: 'Anna uusi salasana',
|
password: 'Anna uusi salasana',
|
||||||
password_config: 'Salasanan määritys',
|
password_config: 'Salasanan määritys',
|
||||||
|
|||||||
@@ -65,9 +65,10 @@ window.localisation.fr = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
"Frais de service facturés par l'administrateur du serveur LNbits pour chaque transaction sortante",
|
"Frais de service facturés par l'administrateur du serveur LNbits pour chaque transaction sortante",
|
||||||
toggle_darkmode: 'Basculer le mode sombre',
|
toggle_darkmode: 'Basculer le mode sombre',
|
||||||
|
payment_reactions: 'Réactions de paiement',
|
||||||
view_swagger_docs: "Voir les documentation de l'API Swagger de LNbits",
|
view_swagger_docs: "Voir les documentation de l'API Swagger de LNbits",
|
||||||
api_docs: "Documentation de l'API",
|
api_docs: "Documentation de l'API",
|
||||||
api_keys_api_docs: "Clés API et documentation de l'API",
|
api_keys_api_docs: 'URL du nœud, clés API et documentation API',
|
||||||
lnbits_version: 'Version de LNbits',
|
lnbits_version: 'Version de LNbits',
|
||||||
runs_on: 'Fonctionne sur',
|
runs_on: 'Fonctionne sur',
|
||||||
credit_hint: 'Appuyez sur Entrée pour créditer le compte',
|
credit_hint: 'Appuyez sur Entrée pour créditer le compte',
|
||||||
@@ -195,6 +196,13 @@ window.localisation.fr = {
|
|||||||
"Autoriser l'accès par IP (cela passera outre les IP bloquées)",
|
"Autoriser l'accès par IP (cela passera outre les IP bloquées)",
|
||||||
enter_ip: "Entrez l'adresse IP et appuyez sur Entrée",
|
enter_ip: "Entrez l'adresse IP et appuyez sur Entrée",
|
||||||
rate_limiter: 'Limiteur de débit',
|
rate_limiter: 'Limiteur de débit',
|
||||||
|
wallet_limiter: 'Limiteur de portefeuille',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Retrait quotidien maximum du portefeuille en sats (0 pour désactiver)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Solde maximum du portefeuille en sats (0 pour désactiver)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minutes et secondes entre les transactions par portefeuille (0 pour désactiver)',
|
||||||
number_of_requests: 'Nombre de requêtes',
|
number_of_requests: 'Nombre de requêtes',
|
||||||
time_unit: 'Unité de temps',
|
time_unit: 'Unité de temps',
|
||||||
minute: 'minute',
|
minute: 'minute',
|
||||||
@@ -213,6 +221,7 @@ window.localisation.fr = {
|
|||||||
account_settings: 'Paramètres du compte',
|
account_settings: 'Paramètres du compte',
|
||||||
signin_with_google: 'Connectez-vous avec Google',
|
signin_with_google: 'Connectez-vous avec Google',
|
||||||
signin_with_github: 'Connectez-vous avec GitHub',
|
signin_with_github: 'Connectez-vous avec GitHub',
|
||||||
|
signin_with_keycloak: 'Connectez-vous avec Keycloak',
|
||||||
username_or_email: "Nom d'utilisateur ou e-mail",
|
username_or_email: "Nom d'utilisateur ou e-mail",
|
||||||
password: 'Mot de passe',
|
password: 'Mot de passe',
|
||||||
password_config: 'Configuration du mot de passe',
|
password_config: 'Configuration du mot de passe',
|
||||||
@@ -235,5 +244,8 @@ window.localisation.fr = {
|
|||||||
auth_provider: "Fournisseur d'authentification",
|
auth_provider: "Fournisseur d'authentification",
|
||||||
my_account: 'Mon compte',
|
my_account: 'Mon compte',
|
||||||
back: 'Retour',
|
back: 'Retour',
|
||||||
logout: 'Déconnexion'
|
logout: 'Déconnexion',
|
||||||
|
look_and_feel: 'Apparence',
|
||||||
|
language: 'Langue',
|
||||||
|
color_scheme: 'Schéma de couleurs'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,9 +62,10 @@ window.localisation.it = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
"Commissione di servizio addebitata dall'amministratore del server LNbits per ogni transazione in uscita",
|
"Commissione di servizio addebitata dall'amministratore del server LNbits per ogni transazione in uscita",
|
||||||
toggle_darkmode: 'Attiva la modalità notturna',
|
toggle_darkmode: 'Attiva la modalità notturna',
|
||||||
|
payment_reactions: 'Reazioni al Pagamento',
|
||||||
view_swagger_docs: "Visualizza i documentazione dell'API Swagger di LNbits",
|
view_swagger_docs: "Visualizza i documentazione dell'API Swagger di LNbits",
|
||||||
api_docs: "Documentazione dell'API",
|
api_docs: "Documentazione dell'API",
|
||||||
api_keys_api_docs: "Chiavi API e documentazione dell'API",
|
api_keys_api_docs: 'URL del nodo, chiavi API e documentazione API',
|
||||||
lnbits_version: 'Versione di LNbits',
|
lnbits_version: 'Versione di LNbits',
|
||||||
runs_on: 'Esegue su',
|
runs_on: 'Esegue su',
|
||||||
credit_hint: 'Premere Invio per accreditare i fondi',
|
credit_hint: 'Premere Invio per accreditare i fondi',
|
||||||
@@ -191,6 +192,13 @@ window.localisation.it = {
|
|||||||
"Consenti l'accesso per IP (sovrascriverà gli IP bloccati)",
|
"Consenti l'accesso per IP (sovrascriverà gli IP bloccati)",
|
||||||
enter_ip: "Inserisci l'IP e premi invio",
|
enter_ip: "Inserisci l'IP e premi invio",
|
||||||
rate_limiter: 'Limitatore di frequenza',
|
rate_limiter: 'Limitatore di frequenza',
|
||||||
|
wallet_limiter: 'Limitatore del Portafoglio',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Prelievo massimo giornaliero dal portafoglio in sats (0 per disabilitare)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Saldo massimo del portafoglio in sats (0 per disabilitare)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minuti e secondi tra transazioni per portafoglio (0 per disabilitare)',
|
||||||
number_of_requests: 'Numero di richieste',
|
number_of_requests: 'Numero di richieste',
|
||||||
time_unit: 'Unità di tempo',
|
time_unit: 'Unità di tempo',
|
||||||
minute: 'minuto',
|
minute: 'minuto',
|
||||||
@@ -210,6 +218,7 @@ window.localisation.it = {
|
|||||||
account_settings: "Impostazioni dell'account",
|
account_settings: "Impostazioni dell'account",
|
||||||
signin_with_google: 'Accedi con Google',
|
signin_with_google: 'Accedi con Google',
|
||||||
signin_with_github: 'Accedi con GitHub',
|
signin_with_github: 'Accedi con GitHub',
|
||||||
|
signin_with_keycloak: 'Accedi con Keycloak',
|
||||||
username_or_email: 'Nome utente o Email',
|
username_or_email: 'Nome utente o Email',
|
||||||
password: 'Password',
|
password: 'Password',
|
||||||
password_config: 'Configurazione della password',
|
password_config: 'Configurazione della password',
|
||||||
@@ -232,5 +241,8 @@ window.localisation.it = {
|
|||||||
auth_provider: 'Provider di Autenticazione',
|
auth_provider: 'Provider di Autenticazione',
|
||||||
my_account: 'Il mio account',
|
my_account: 'Il mio account',
|
||||||
back: 'Indietro',
|
back: 'Indietro',
|
||||||
logout: 'Esci'
|
logout: 'Esci',
|
||||||
|
look_and_feel: 'Aspetto e Comportamento',
|
||||||
|
language: 'Lingua',
|
||||||
|
color_scheme: 'Schema dei colori'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,9 +59,10 @@ window.localisation.jp = {
|
|||||||
service_fee_max: '取引手数料:%{amount}%(最大%{max}サトシ)',
|
service_fee_max: '取引手数料:%{amount}%(最大%{max}サトシ)',
|
||||||
service_fee_tooltip: 'LNbitsサーバー管理者が発生する送金ごとの手数料',
|
service_fee_tooltip: 'LNbitsサーバー管理者が発生する送金ごとの手数料',
|
||||||
toggle_darkmode: 'ダークモードを切り替える',
|
toggle_darkmode: 'ダークモードを切り替える',
|
||||||
|
payment_reactions: '支払いの反応',
|
||||||
view_swagger_docs: 'Swaggerドキュメントを表示',
|
view_swagger_docs: 'Swaggerドキュメントを表示',
|
||||||
api_docs: 'APIドキュメント',
|
api_docs: 'APIドキュメント',
|
||||||
api_keys_api_docs: 'APIキーとAPIドキュメント',
|
api_keys_api_docs: 'ノードURL、APIキー、APIドキュメント',
|
||||||
lnbits_version: 'LNbits バージョン',
|
lnbits_version: 'LNbits バージョン',
|
||||||
runs_on: 'で実行',
|
runs_on: 'で実行',
|
||||||
credit_hint:
|
credit_hint:
|
||||||
@@ -188,6 +189,12 @@ window.localisation.jp = {
|
|||||||
'IPによるアクセスを許可する(ブロックされたIPを上書きします)',
|
'IPによるアクセスを許可する(ブロックされたIPを上書きします)',
|
||||||
enter_ip: 'IPを入力してエンターキーを押してください',
|
enter_ip: 'IPを入力してエンターキーを押してください',
|
||||||
rate_limiter: 'レートリミッター',
|
rate_limiter: 'レートリミッター',
|
||||||
|
wallet_limiter: 'ウォレットリミッター',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'1日あたりの最大ウォレット出金額をsatsで入力してください(0 で無効)。',
|
||||||
|
wallet_max_ballance: 'ウォレットの最大残高(sats)(0は無効)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'トランザクション間の最小秒数(ウォレットごと)(0は無効)',
|
||||||
number_of_requests: 'リクエストの数',
|
number_of_requests: 'リクエストの数',
|
||||||
time_unit: '時間単位',
|
time_unit: '時間単位',
|
||||||
minute: '分',
|
minute: '分',
|
||||||
@@ -207,6 +214,7 @@ window.localisation.jp = {
|
|||||||
account_settings: 'アカウント設定',
|
account_settings: 'アカウント設定',
|
||||||
signin_with_google: 'Googleでサインイン',
|
signin_with_google: 'Googleでサインイン',
|
||||||
signin_with_github: 'GitHubでサインイン',
|
signin_with_github: 'GitHubでサインイン',
|
||||||
|
signin_with_keycloak: 'Keycloakでサインイン',
|
||||||
username_or_email: 'ユーザー名またはメールアドレス',
|
username_or_email: 'ユーザー名またはメールアドレス',
|
||||||
password: 'パスワード',
|
password: 'パスワード',
|
||||||
password_config: 'パスワード設定',
|
password_config: 'パスワード設定',
|
||||||
@@ -229,5 +237,8 @@ window.localisation.jp = {
|
|||||||
auth_provider: '認証プロバイダ',
|
auth_provider: '認証プロバイダ',
|
||||||
my_account: 'マイアカウント',
|
my_account: 'マイアカウント',
|
||||||
back: '戻る',
|
back: '戻る',
|
||||||
logout: 'ログアウト'
|
logout: 'ログアウト',
|
||||||
|
look_and_feel: 'ルック・アンド・フィール',
|
||||||
|
language: '言語',
|
||||||
|
color_scheme: 'カラースキーム'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ window.localisation.kr = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'지불 결제 시마다 LNBits 서버 관리자에게 납부되는 서비스 수수료',
|
'지불 결제 시마다 LNBits 서버 관리자에게 납부되는 서비스 수수료',
|
||||||
toggle_darkmode: '다크 모드 전환',
|
toggle_darkmode: '다크 모드 전환',
|
||||||
|
payment_reactions: '결제 반응',
|
||||||
view_swagger_docs: 'LNbits Swagger API 문서를 봅니다',
|
view_swagger_docs: 'LNbits Swagger API 문서를 봅니다',
|
||||||
api_docs: 'API 문서',
|
api_docs: 'API 문서',
|
||||||
api_keys_api_docs: 'API 키와 API 문서',
|
api_keys_api_docs: '노드 URL, API 키와 API 문서',
|
||||||
lnbits_version: 'LNbits 버전',
|
lnbits_version: 'LNbits 버전',
|
||||||
runs_on: 'Runs on',
|
runs_on: 'Runs on',
|
||||||
credit_hint: '계정에 자금을 넣으려면 Enter를 눌러주세요',
|
credit_hint: '계정에 자금을 넣으려면 Enter를 눌러주세요',
|
||||||
@@ -187,6 +188,11 @@ window.localisation.kr = {
|
|||||||
allow_access_hint: 'IP 기준으로 접속 허용하기 (차단한 IP들을 무시합니다)',
|
allow_access_hint: 'IP 기준으로 접속 허용하기 (차단한 IP들을 무시합니다)',
|
||||||
enter_ip: 'IP 주소를 입력하고 Enter를 눌러주세요',
|
enter_ip: 'IP 주소를 입력하고 Enter를 눌러주세요',
|
||||||
rate_limiter: '횟수로 제한하기',
|
rate_limiter: '횟수로 제한하기',
|
||||||
|
wallet_limiter: '지갑 제한기',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'일일 최대 지갑 출금액(sats) (0은 비활성화)',
|
||||||
|
wallet_max_ballance: '지갑 최대 잔액(sats) (0은 비활성화)',
|
||||||
|
wallet_limit_secs_between_trans: '지갑 당 거래 사이 최소 초 (0은 비활성화)',
|
||||||
number_of_requests: '요청 횟수',
|
number_of_requests: '요청 횟수',
|
||||||
time_unit: '시간 단위',
|
time_unit: '시간 단위',
|
||||||
minute: '분',
|
minute: '분',
|
||||||
@@ -205,6 +211,7 @@ window.localisation.kr = {
|
|||||||
account_settings: '계정 설정',
|
account_settings: '계정 설정',
|
||||||
signin_with_google: 'Google으로 로그인',
|
signin_with_google: 'Google으로 로그인',
|
||||||
signin_with_github: 'GitHub으로 로그인',
|
signin_with_github: 'GitHub으로 로그인',
|
||||||
|
signin_with_keycloak: 'Keycloak으로 로그인',
|
||||||
username_or_email: '사용자 이름 또는 이메일',
|
username_or_email: '사용자 이름 또는 이메일',
|
||||||
password: '비밀번호',
|
password: '비밀번호',
|
||||||
password_config: '비밀번호 설정',
|
password_config: '비밀번호 설정',
|
||||||
@@ -227,5 +234,8 @@ window.localisation.kr = {
|
|||||||
auth_provider: '인증 제공자',
|
auth_provider: '인증 제공자',
|
||||||
my_account: '내 계정',
|
my_account: '내 계정',
|
||||||
back: '뒤로',
|
back: '뒤로',
|
||||||
logout: '로그아웃'
|
logout: '로그아웃',
|
||||||
|
look_and_feel: '외관과 느낌',
|
||||||
|
language: '언어',
|
||||||
|
color_scheme: '색상 구성'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,9 +63,10 @@ window.localisation.nl = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Transactiekosten in rekening gebracht door de LNbits serverbeheerder per uitgaande transactie',
|
'Transactiekosten in rekening gebracht door de LNbits serverbeheerder per uitgaande transactie',
|
||||||
toggle_darkmode: 'Donkere modus aan/uit',
|
toggle_darkmode: 'Donkere modus aan/uit',
|
||||||
|
payment_reactions: 'Betalingsreacties',
|
||||||
view_swagger_docs: 'Bekijk LNbits Swagger API-documentatie',
|
view_swagger_docs: 'Bekijk LNbits Swagger API-documentatie',
|
||||||
api_docs: 'API-documentatie',
|
api_docs: 'API-documentatie',
|
||||||
api_keys_api_docs: 'API-sleutels en API-documentatie',
|
api_keys_api_docs: 'Node URL, API-sleutels en API-documentatie',
|
||||||
lnbits_version: 'LNbits-versie',
|
lnbits_version: 'LNbits-versie',
|
||||||
runs_on: 'Draait op',
|
runs_on: 'Draait op',
|
||||||
credit_hint: 'Druk op Enter om de rekening te crediteren',
|
credit_hint: 'Druk op Enter om de rekening te crediteren',
|
||||||
@@ -191,6 +192,13 @@ window.localisation.nl = {
|
|||||||
"Toegang verlenen op basis van IP (zal geblokkeerde IP's overschrijven)",
|
"Toegang verlenen op basis van IP (zal geblokkeerde IP's overschrijven)",
|
||||||
enter_ip: 'Voer IP in en druk op enter',
|
enter_ip: 'Voer IP in en druk op enter',
|
||||||
rate_limiter: 'Snelheidsbegrenzer',
|
rate_limiter: 'Snelheidsbegrenzer',
|
||||||
|
wallet_limiter: 'Portemonnee Limietsteller',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maximale dagelijkse opname van wallet in sats (0 om uit te schakelen)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Maximale portefeuillesaldo in sats (0 om uit te schakelen)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Min seconden tussen transacties per portemonnee (0 om uit te schakelen)',
|
||||||
number_of_requests: 'Aantal verzoeken',
|
number_of_requests: 'Aantal verzoeken',
|
||||||
time_unit: 'Tijdeenheid',
|
time_unit: 'Tijdeenheid',
|
||||||
minute: 'minuut',
|
minute: 'minuut',
|
||||||
@@ -209,6 +217,7 @@ window.localisation.nl = {
|
|||||||
account_settings: 'Accountinstellingen',
|
account_settings: 'Accountinstellingen',
|
||||||
signin_with_google: 'Inloggen met Google',
|
signin_with_google: 'Inloggen met Google',
|
||||||
signin_with_github: 'Inloggen met GitHub',
|
signin_with_github: 'Inloggen met GitHub',
|
||||||
|
signin_with_keycloak: 'Inloggen met Keycloak',
|
||||||
username_or_email: 'Gebruikersnaam of e-mail',
|
username_or_email: 'Gebruikersnaam of e-mail',
|
||||||
password: 'Wachtwoord',
|
password: 'Wachtwoord',
|
||||||
password_config: 'Wachtwoordconfiguratie',
|
password_config: 'Wachtwoordconfiguratie',
|
||||||
@@ -231,5 +240,8 @@ window.localisation.nl = {
|
|||||||
auth_provider: 'Auth Provider',
|
auth_provider: 'Auth Provider',
|
||||||
my_account: 'Mijn Account',
|
my_account: 'Mijn Account',
|
||||||
back: 'Terug',
|
back: 'Terug',
|
||||||
logout: 'Afmelden'
|
logout: 'Afmelden',
|
||||||
|
look_and_feel: 'Uiterlijk en gedrag',
|
||||||
|
language: 'Taal',
|
||||||
|
color_scheme: 'Kleurenschema'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.pi = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
"Service fee charged by the LNbits server admin per goin' transaction",
|
"Service fee charged by the LNbits server admin per goin' transaction",
|
||||||
toggle_darkmode: 'Toggle Dark Mode, arr!',
|
toggle_darkmode: 'Toggle Dark Mode, arr!',
|
||||||
|
payment_reactions: 'Payment Reactions',
|
||||||
view_swagger_docs: 'View LNbits Swagger API docs and learn the secrets',
|
view_swagger_docs: 'View LNbits Swagger API docs and learn the secrets',
|
||||||
api_docs: 'API docs for the scallywags',
|
api_docs: 'API docs for the scallywags',
|
||||||
api_keys_api_docs: 'API keys and API docs',
|
api_keys_api_docs: 'Node URL, API keys and API docs',
|
||||||
lnbits_version: 'LNbits version, arr!',
|
lnbits_version: 'LNbits version, arr!',
|
||||||
runs_on: 'Runs on, matey',
|
runs_on: 'Runs on, matey',
|
||||||
credit_hint: 'Press Enter to credit account and make it richer',
|
credit_hint: 'Press Enter to credit account and make it richer',
|
||||||
@@ -189,6 +190,12 @@ window.localisation.pi = {
|
|||||||
allow_access_hint: 'Grant permission by IP (will override barred IPs)',
|
allow_access_hint: 'Grant permission by IP (will override barred IPs)',
|
||||||
enter_ip: 'Enter IP and hit enter',
|
enter_ip: 'Enter IP and hit enter',
|
||||||
rate_limiter: 'Rate Limiter',
|
rate_limiter: 'Rate Limiter',
|
||||||
|
wallet_limiter: 'Pouch Limitar',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Max daily wallet withdrawal in sats (0 ter disable)',
|
||||||
|
wallet_max_ballance: 'Purse max heaviness in sats (0 fer scuttle)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
"Min secs 'tween transactions per wallet (0 to disable)",
|
||||||
number_of_requests: "Number o' requests",
|
number_of_requests: "Number o' requests",
|
||||||
time_unit: "time bein'",
|
time_unit: "time bein'",
|
||||||
minute: 'minnit',
|
minute: 'minnit',
|
||||||
@@ -207,6 +214,7 @@ window.localisation.pi = {
|
|||||||
account_settings: "Account Settin's",
|
account_settings: "Account Settin's",
|
||||||
signin_with_google: "Sign in wit' Google",
|
signin_with_google: "Sign in wit' Google",
|
||||||
signin_with_github: "Sign in wit' GitHub",
|
signin_with_github: "Sign in wit' GitHub",
|
||||||
|
signin_with_keycloak: "Sign in wit' Keycloak",
|
||||||
username_or_email: 'Usarrrname or Email',
|
username_or_email: 'Usarrrname or Email',
|
||||||
password: 'Passwarrd',
|
password: 'Passwarrd',
|
||||||
password_config: 'Passwarrd Config',
|
password_config: 'Passwarrd Config',
|
||||||
@@ -229,5 +237,8 @@ window.localisation.pi = {
|
|||||||
auth_provider: 'Auth Provider becometh Auth Provider, ye see?',
|
auth_provider: 'Auth Provider becometh Auth Provider, ye see?',
|
||||||
my_account: 'Me Arrrccount',
|
my_account: 'Me Arrrccount',
|
||||||
back: 'Return',
|
back: 'Return',
|
||||||
logout: 'Log out yer session'
|
logout: 'Log out yer session',
|
||||||
|
look_and_feel: 'Look and Feel',
|
||||||
|
language: 'Langwidge',
|
||||||
|
color_scheme: 'Colour Scheme'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ window.localisation.pl = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Opłata serwisowa pobierana przez administratora serwera LNbits za każdą wychodzącą transakcję',
|
'Opłata serwisowa pobierana przez administratora serwera LNbits za każdą wychodzącą transakcję',
|
||||||
toggle_darkmode: 'Tryb nocny',
|
toggle_darkmode: 'Tryb nocny',
|
||||||
|
payment_reactions: 'Reakcje na płatność',
|
||||||
view_swagger_docs: 'Dokumentacja Swagger API',
|
view_swagger_docs: 'Dokumentacja Swagger API',
|
||||||
api_docs: 'Dokumentacja API',
|
api_docs: 'Dokumentacja API',
|
||||||
api_keys_api_docs: 'Klucze API i dokumentacja API',
|
api_keys_api_docs: 'Adres URL węzła, klucze API i dokumentacja API',
|
||||||
lnbits_version: 'Wersja LNbits',
|
lnbits_version: 'Wersja LNbits',
|
||||||
runs_on: 'Działa na',
|
runs_on: 'Działa na',
|
||||||
credit_hint: 'Naciśnij Enter aby doładować konto',
|
credit_hint: 'Naciśnij Enter aby doładować konto',
|
||||||
@@ -188,6 +189,12 @@ window.localisation.pl = {
|
|||||||
'Zezwól na dostęp przez IP (zignoruje zablokowane adresy IP)',
|
'Zezwól na dostęp przez IP (zignoruje zablokowane adresy IP)',
|
||||||
enter_ip: 'Wpisz adres IP i naciśnij enter',
|
enter_ip: 'Wpisz adres IP i naciśnij enter',
|
||||||
rate_limiter: 'Ogranicznik Częstotliwości',
|
rate_limiter: 'Ogranicznik Częstotliwości',
|
||||||
|
wallet_limiter: 'Ogranicznik Portfela',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maksymalna dzienna wypłata z portfela w satoshi (0 aby wyłączyć)',
|
||||||
|
wallet_max_ballance: 'Maksymalny stan portfela w satoshi (0 aby wyłączyć)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Min sekund pomiędzy transakcjami na portfel (0 aby wyłączyć)',
|
||||||
number_of_requests: 'Liczba żądań',
|
number_of_requests: 'Liczba żądań',
|
||||||
time_unit: 'Jednostka czasu',
|
time_unit: 'Jednostka czasu',
|
||||||
minute: 'minuta',
|
minute: 'minuta',
|
||||||
@@ -206,6 +213,7 @@ window.localisation.pl = {
|
|||||||
account_settings: 'Ustawienia konta',
|
account_settings: 'Ustawienia konta',
|
||||||
signin_with_google: 'Zaloguj się przez Google',
|
signin_with_google: 'Zaloguj się przez Google',
|
||||||
signin_with_github: 'Zaloguj się przez GitHub',
|
signin_with_github: 'Zaloguj się przez GitHub',
|
||||||
|
signin_with_keycloak: 'Zaloguj się przez Keycloak',
|
||||||
username_or_email: 'Nazwa użytkownika lub Email',
|
username_or_email: 'Nazwa użytkownika lub Email',
|
||||||
password: 'Hasło',
|
password: 'Hasło',
|
||||||
password_config: 'Konfiguracja Hasła',
|
password_config: 'Konfiguracja Hasła',
|
||||||
@@ -228,5 +236,8 @@ window.localisation.pl = {
|
|||||||
auth_provider: 'Dostawca uwierzytelniania',
|
auth_provider: 'Dostawca uwierzytelniania',
|
||||||
my_account: 'Moje Konto',
|
my_account: 'Moje Konto',
|
||||||
back: 'Wstecz',
|
back: 'Wstecz',
|
||||||
logout: 'Wyloguj'
|
logout: 'Wyloguj',
|
||||||
|
look_and_feel: 'Wygląd i zachowanie',
|
||||||
|
language: 'Język',
|
||||||
|
color_scheme: 'Schemat kolorów'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.pt = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Taxa de serviço cobrada pelo administrador do servidor LNbits por transação de saída',
|
'Taxa de serviço cobrada pelo administrador do servidor LNbits por transação de saída',
|
||||||
toggle_darkmode: 'Alternar modo escuro',
|
toggle_darkmode: 'Alternar modo escuro',
|
||||||
|
payment_reactions: 'Reações de Pagamento',
|
||||||
view_swagger_docs: 'Ver a documentação da API do LNbits Swagger',
|
view_swagger_docs: 'Ver a documentação da API do LNbits Swagger',
|
||||||
api_docs: 'Documentação da API',
|
api_docs: 'Documentação da API',
|
||||||
api_keys_api_docs: 'Chaves de API e documentação de API',
|
api_keys_api_docs: 'URL do Nó, chaves de API e documentação de API',
|
||||||
lnbits_version: 'Versão do LNbits',
|
lnbits_version: 'Versão do LNbits',
|
||||||
runs_on: 'Executa em',
|
runs_on: 'Executa em',
|
||||||
credit_hint: 'Pressione Enter para creditar a conta',
|
credit_hint: 'Pressione Enter para creditar a conta',
|
||||||
@@ -189,6 +190,12 @@ window.localisation.pt = {
|
|||||||
allow_access_hint: 'Permitir acesso por IP (substituirá IPs bloqueados)',
|
allow_access_hint: 'Permitir acesso por IP (substituirá IPs bloqueados)',
|
||||||
enter_ip: 'Digite o IP e pressione enter.',
|
enter_ip: 'Digite o IP e pressione enter.',
|
||||||
rate_limiter: 'Limitador de Taxa',
|
rate_limiter: 'Limitador de Taxa',
|
||||||
|
wallet_limiter: 'Limitador de Carteira',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Limite diário máximo de saque da carteira em sats (0 para desativar)',
|
||||||
|
wallet_max_ballance: 'Saldo máximo da carteira em sats (0 para desativar)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minutos seg. entre transações por carteira (0 para desativar)',
|
||||||
number_of_requests: 'Número de solicitações',
|
number_of_requests: 'Número de solicitações',
|
||||||
time_unit: 'Unidade de tempo',
|
time_unit: 'Unidade de tempo',
|
||||||
minute: 'minuto',
|
minute: 'minuto',
|
||||||
@@ -207,6 +214,7 @@ window.localisation.pt = {
|
|||||||
account_settings: 'Configurações da Conta',
|
account_settings: 'Configurações da Conta',
|
||||||
signin_with_google: 'Entrar com o Google',
|
signin_with_google: 'Entrar com o Google',
|
||||||
signin_with_github: 'Entrar com o GitHub',
|
signin_with_github: 'Entrar com o GitHub',
|
||||||
|
signin_with_keycloak: 'Entrar com o Keycloak',
|
||||||
username_or_email: 'Nome de usuário ou Email',
|
username_or_email: 'Nome de usuário ou Email',
|
||||||
password: 'Senha',
|
password: 'Senha',
|
||||||
password_config: 'Configuração de Senha',
|
password_config: 'Configuração de Senha',
|
||||||
@@ -229,5 +237,8 @@ window.localisation.pt = {
|
|||||||
auth_provider: 'Provedor de Autenticação',
|
auth_provider: 'Provedor de Autenticação',
|
||||||
my_account: 'Minha Conta',
|
my_account: 'Minha Conta',
|
||||||
back: 'Voltar',
|
back: 'Voltar',
|
||||||
logout: 'Sair'
|
logout: 'Sair',
|
||||||
|
look_and_feel: 'Aparência e Sensação',
|
||||||
|
language: 'Idioma',
|
||||||
|
color_scheme: 'Esquema de Cores'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ window.localisation.sk = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
'Servisný poplatok účtovaný správcom LNbits servera za odchádzajúcu transakciu',
|
'Servisný poplatok účtovaný správcom LNbits servera za odchádzajúcu transakciu',
|
||||||
toggle_darkmode: 'Prepnúť Tmavý režim',
|
toggle_darkmode: 'Prepnúť Tmavý režim',
|
||||||
|
payment_reactions: 'Reakcie na platbu',
|
||||||
view_swagger_docs: 'Zobraziť LNbits Swagger API dokumentáciu',
|
view_swagger_docs: 'Zobraziť LNbits Swagger API dokumentáciu',
|
||||||
api_docs: 'API dokumentácia',
|
api_docs: 'API dokumentácia',
|
||||||
api_keys_api_docs: 'API kľúče a API dokumentácia',
|
api_keys_api_docs: 'Adresa uzla, API kľúče a API dokumentácia',
|
||||||
lnbits_version: 'Verzia LNbits',
|
lnbits_version: 'Verzia LNbits',
|
||||||
runs_on: 'Beží na',
|
runs_on: 'Beží na',
|
||||||
credit_hint: 'Stlačte Enter pre pripísanie na účet',
|
credit_hint: 'Stlačte Enter pre pripísanie na účet',
|
||||||
@@ -187,6 +188,13 @@ window.localisation.sk = {
|
|||||||
allow_access_hint: 'Povoliť prístup podľa IP (prebije blokované IP)',
|
allow_access_hint: 'Povoliť prístup podľa IP (prebije blokované IP)',
|
||||||
enter_ip: 'Zadajte IP a stlačte enter',
|
enter_ip: 'Zadajte IP a stlačte enter',
|
||||||
rate_limiter: 'Obmedzovač počtu požiadaviek',
|
rate_limiter: 'Obmedzovač počtu požiadaviek',
|
||||||
|
wallet_limiter: 'Obmedzovač peňaženky',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Maximálny denný výber z peňaženky v satošiach (0 pre zrušenie)',
|
||||||
|
wallet_max_ballance:
|
||||||
|
'Maximálny zostatok v peňaženke v satošiach (0 pre deaktiváciu)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Minimálny počet sekúnd medzi transakciami na peňaženku (0 na deaktiváciu)',
|
||||||
number_of_requests: 'Počet požiadaviek',
|
number_of_requests: 'Počet požiadaviek',
|
||||||
time_unit: 'Časová jednotka',
|
time_unit: 'Časová jednotka',
|
||||||
minute: 'minúta',
|
minute: 'minúta',
|
||||||
@@ -203,8 +211,9 @@ window.localisation.sk = {
|
|||||||
login_to_account: 'Prihláste sa do vášho účtu',
|
login_to_account: 'Prihláste sa do vášho účtu',
|
||||||
create_account: 'Vytvoriť účet',
|
create_account: 'Vytvoriť účet',
|
||||||
account_settings: 'Nastavenia účtu',
|
account_settings: 'Nastavenia účtu',
|
||||||
signin_with_google: 'Prihlásiť sa cez Google',
|
signin_with_google: 'Prihlásiť sa pomocou Google',
|
||||||
signin_with_github: 'Prihláste sa pomocou GitHub',
|
signin_with_github: 'Prihlásiť sa pomocou GitHub',
|
||||||
|
signin_with_keycloak: 'Prihlásiť sa pomocou Keycloak',
|
||||||
username_or_email: 'Používateľské meno alebo email',
|
username_or_email: 'Používateľské meno alebo email',
|
||||||
password: 'Heslo',
|
password: 'Heslo',
|
||||||
password_config: 'Konfigurácia hesla',
|
password_config: 'Konfigurácia hesla',
|
||||||
@@ -227,5 +236,8 @@ window.localisation.sk = {
|
|||||||
auth_provider: 'Poskytovateľ autentifikácie',
|
auth_provider: 'Poskytovateľ autentifikácie',
|
||||||
my_account: 'Môj účet',
|
my_account: 'Môj účet',
|
||||||
back: 'Späť',
|
back: 'Späť',
|
||||||
logout: 'Odhlásiť sa'
|
logout: 'Odhlásiť sa',
|
||||||
|
look_and_feel: 'Vzhľad a dojem',
|
||||||
|
language: 'Jazyk',
|
||||||
|
color_scheme: 'Farebná schéma'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,9 +61,10 @@ window.localisation.we = {
|
|||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
"Ffi gwasanaeth a godir gan weinyddwr gweinydd LNbits ym mhob trafodiad sy'n mynd allan",
|
"Ffi gwasanaeth a godir gan weinyddwr gweinydd LNbits ym mhob trafodiad sy'n mynd allan",
|
||||||
toggle_darkmode: 'Toglo Modd Tywyll',
|
toggle_darkmode: 'Toglo Modd Tywyll',
|
||||||
|
payment_reactions: 'Adweithiau Talu',
|
||||||
view_swagger_docs: 'Gweld dogfennau API LNbits Swagger',
|
view_swagger_docs: 'Gweld dogfennau API LNbits Swagger',
|
||||||
api_docs: 'Dogfennau API',
|
api_docs: 'Dogfennau API',
|
||||||
api_keys_api_docs: 'Allweddi API a dogfennau API',
|
api_keys_api_docs: 'URL y nod, allweddi API a dogfennau API',
|
||||||
lnbits_version: 'Fersiwn LNbits',
|
lnbits_version: 'Fersiwn LNbits',
|
||||||
runs_on: 'Yn rhedeg ymlaen',
|
runs_on: 'Yn rhedeg ymlaen',
|
||||||
credit_hint: 'Pwyswch Enter i gyfrif credyd',
|
credit_hint: 'Pwyswch Enter i gyfrif credyd',
|
||||||
@@ -188,6 +189,12 @@ window.localisation.we = {
|
|||||||
"Caniatáu mynediad gan IP (bydd yn diystyru IPs sydd wedi'u blocio)",
|
"Caniatáu mynediad gan IP (bydd yn diystyru IPs sydd wedi'u blocio)",
|
||||||
enter_ip: 'Rhowch IP a gwasgwch enter',
|
enter_ip: 'Rhowch IP a gwasgwch enter',
|
||||||
rate_limiter: 'Cyfyngydd Cyfradd',
|
rate_limiter: 'Cyfyngydd Cyfradd',
|
||||||
|
wallet_limiter: 'Cyfyngwr Waled',
|
||||||
|
wallet_limit_max_withdraw_per_day:
|
||||||
|
'Uchafswm tynnu’n ôl waled dyddiol mewn sats (0 i analluogi)',
|
||||||
|
wallet_max_ballance: 'Uchafswm balans y waled mewn sats (0 i analluogi)',
|
||||||
|
wallet_limit_secs_between_trans:
|
||||||
|
'Eiliadau lleiaf rhwng trafodion fesul waled (0 i analluogi)',
|
||||||
number_of_requests: 'Nifer y ceisiadau',
|
number_of_requests: 'Nifer y ceisiadau',
|
||||||
time_unit: 'Uned amser',
|
time_unit: 'Uned amser',
|
||||||
minute: 'munud',
|
minute: 'munud',
|
||||||
@@ -206,6 +213,7 @@ window.localisation.we = {
|
|||||||
account_settings: 'Gosodiadau Cyfrif',
|
account_settings: 'Gosodiadau Cyfrif',
|
||||||
signin_with_google: 'Mewngofnodi gyda Google',
|
signin_with_google: 'Mewngofnodi gyda Google',
|
||||||
signin_with_github: 'Mewngofnodi gyda GitHub',
|
signin_with_github: 'Mewngofnodi gyda GitHub',
|
||||||
|
signin_with_keycloak: 'Mewngofnodi gyda Keycloak',
|
||||||
username_or_email: 'Defnyddiwr neu E-bost',
|
username_or_email: 'Defnyddiwr neu E-bost',
|
||||||
password: 'Cyfrinair',
|
password: 'Cyfrinair',
|
||||||
password_config: 'Ffurfweddiad Cyfrinair',
|
password_config: 'Ffurfweddiad Cyfrinair',
|
||||||
@@ -228,5 +236,8 @@ window.localisation.we = {
|
|||||||
auth_provider: 'Darparwr Dilysiad',
|
auth_provider: 'Darparwr Dilysiad',
|
||||||
my_account: 'Fy Nghyfrif',
|
my_account: 'Fy Nghyfrif',
|
||||||
back: 'Yn ôl',
|
back: 'Yn ôl',
|
||||||
logout: 'Allgofnodi'
|
logout: 'Allgofnodi',
|
||||||
|
look_and_feel: 'Edrych a Theimlo',
|
||||||
|
language: 'Iaith',
|
||||||
|
color_scheme: 'Cynllun Lliw'
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -6,6 +6,12 @@ new Vue({
|
|||||||
user: null,
|
user: null,
|
||||||
hasUsername: false,
|
hasUsername: false,
|
||||||
showUserId: false,
|
showUserId: false,
|
||||||
|
reactionOptions: [
|
||||||
|
'None',
|
||||||
|
'confettiBothSides',
|
||||||
|
'confettiFireworks',
|
||||||
|
'confettiStars'
|
||||||
|
],
|
||||||
tab: 'user',
|
tab: 'user',
|
||||||
passwordData: {
|
passwordData: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -27,6 +33,9 @@ new Vue({
|
|||||||
this.$q.dark.toggle()
|
this.$q.dark.toggle()
|
||||||
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
|
this.$q.localStorage.set('lnbits.darkMode', this.$q.dark.isActive)
|
||||||
},
|
},
|
||||||
|
reactionChoiceFunc: function () {
|
||||||
|
this.$q.localStorage.set('lnbits.reactions', this.reactionChoice)
|
||||||
|
},
|
||||||
changeColor: function (newValue) {
|
changeColor: function (newValue) {
|
||||||
document.body.setAttribute('data-theme', newValue)
|
document.body.setAttribute('data-theme', newValue)
|
||||||
this.$q.localStorage.set('lnbits.theme', newValue)
|
this.$q.localStorage.set('lnbits.theme', newValue)
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ window.LNbits = {
|
|||||||
let listener = ev => {
|
let listener = ev => {
|
||||||
cb(JSON.parse(ev.data))
|
cb(JSON.parse(ev.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.listenersCount = this.listenersCount || {[wallet.inkey]: 0}
|
this.listenersCount = this.listenersCount || {[wallet.inkey]: 0}
|
||||||
this.listenersCount[wallet.inkey]++
|
this.listenersCount[wallet.inkey]++
|
||||||
|
|
||||||
@@ -444,6 +443,7 @@ window.windowMixin = {
|
|||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
toggleSubs: true,
|
toggleSubs: true,
|
||||||
|
reactionChoice: 'confettiBothSides',
|
||||||
isUserAuthorized: false,
|
isUserAuthorized: false,
|
||||||
g: {
|
g: {
|
||||||
offline: !navigator.onLine,
|
offline: !navigator.onLine,
|
||||||
@@ -523,6 +523,9 @@ window.windowMixin = {
|
|||||||
} else {
|
} else {
|
||||||
this.$q.dark.set(true)
|
this.$q.dark.set(true)
|
||||||
}
|
}
|
||||||
|
this.reactionChoice =
|
||||||
|
this.$q.localStorage.getItem('lnbits.reactions') || 'confettiBothSides'
|
||||||
|
|
||||||
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
|
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
|
||||||
|
|
||||||
let locale = this.$q.localStorage.getItem('lnbits.lang')
|
let locale = this.$q.localStorage.getItem('lnbits.lang')
|
||||||
|
|||||||
@@ -0,0 +1,560 @@
|
|||||||
|
function eventReactionWebocket(event_id) {
|
||||||
|
localUrl = ''
|
||||||
|
reaction = localStorage.getItem('lnbits.reactions')
|
||||||
|
if (!reaction || reaction === 'None') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (location.protocol !== 'http:') {
|
||||||
|
localUrl = 'wss://' + location.host + '/api/v1/ws/' + event_id
|
||||||
|
} else {
|
||||||
|
localUrl = 'ws://' + location.host + '/api/v1/ws/' + event_id
|
||||||
|
}
|
||||||
|
connection = new WebSocket(localUrl)
|
||||||
|
connection.onmessage = function (e) {
|
||||||
|
try {
|
||||||
|
const parsedData = JSON.parse(e.data)
|
||||||
|
if (parsedData.payment.amount < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
reaction = localStorage.getItem('lnbits.reactions')
|
||||||
|
if (reaction) {
|
||||||
|
window[reaction.split('|')[1]]()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function confettiBothSides() {
|
||||||
|
document.getElementById('vue').disabled = true
|
||||||
|
var end = Date.now() + 2 * 1000
|
||||||
|
var colors = ['#FFD700', '#ffffff']
|
||||||
|
function frame() {
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 60,
|
||||||
|
spread: 55,
|
||||||
|
origin: {x: 0},
|
||||||
|
colors: colors,
|
||||||
|
zIndex: 999999
|
||||||
|
})
|
||||||
|
confetti({
|
||||||
|
particleCount: 2,
|
||||||
|
angle: 120,
|
||||||
|
spread: 55,
|
||||||
|
origin: {x: 1},
|
||||||
|
colors: colors,
|
||||||
|
zIndex: 999999
|
||||||
|
})
|
||||||
|
if (Date.now() < end) {
|
||||||
|
requestAnimationFrame(frame)
|
||||||
|
} else {
|
||||||
|
document.getElementById('vue').disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame()
|
||||||
|
}
|
||||||
|
function confettiFireworks() {
|
||||||
|
var duration = 3 * 1000
|
||||||
|
var animationEnd = Date.now() + duration
|
||||||
|
var defaults = {startVelocity: 30, spread: 360, ticks: 60, zIndex: 0}
|
||||||
|
|
||||||
|
function randomInRange(min, max) {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
var interval = setInterval(function () {
|
||||||
|
var timeLeft = animationEnd - Date.now()
|
||||||
|
|
||||||
|
if (timeLeft <= 0) {
|
||||||
|
return clearInterval(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
var particleCount = 5 * (timeLeft / duration)
|
||||||
|
// since particles fall down, start a bit higher than random
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount,
|
||||||
|
origin: {x: randomInRange(0.1, 0.3), y: Math.random() - 0.2}
|
||||||
|
})
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount,
|
||||||
|
origin: {x: randomInRange(0.7, 0.9), y: Math.random() - 0.2}
|
||||||
|
})
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
|
||||||
|
function confettiStars() {
|
||||||
|
var defaults = {
|
||||||
|
spread: 360,
|
||||||
|
ticks: 50,
|
||||||
|
gravity: 0,
|
||||||
|
decay: 0.94,
|
||||||
|
startVelocity: 30,
|
||||||
|
colors: ['FFE400', 'FFBD00', 'E89400', 'FFCA6C', 'FDFFB8']
|
||||||
|
}
|
||||||
|
|
||||||
|
function shoot() {
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount: 40,
|
||||||
|
scalar: 1.2,
|
||||||
|
shapes: ['star']
|
||||||
|
})
|
||||||
|
|
||||||
|
confetti({
|
||||||
|
...defaults,
|
||||||
|
particleCount: 10,
|
||||||
|
scalar: 0.75,
|
||||||
|
shapes: ['circle']
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(shoot, 0)
|
||||||
|
setTimeout(shoot, 100)
|
||||||
|
setTimeout(shoot, 200)
|
||||||
|
setTimeout(shoot, 0)
|
||||||
|
setTimeout(shoot, 100)
|
||||||
|
setTimeout(shoot, 200)
|
||||||
|
}
|
||||||
|
!(function (t, e) {
|
||||||
|
!(function t(e, n, a, i) {
|
||||||
|
var o = !!(
|
||||||
|
e.Worker &&
|
||||||
|
e.Blob &&
|
||||||
|
e.Promise &&
|
||||||
|
e.OffscreenCanvas &&
|
||||||
|
e.OffscreenCanvasRenderingContext2D &&
|
||||||
|
e.HTMLCanvasElement &&
|
||||||
|
e.HTMLCanvasElement.prototype.transferControlToOffscreen &&
|
||||||
|
e.URL &&
|
||||||
|
e.URL.createObjectURL
|
||||||
|
)
|
||||||
|
function r() {}
|
||||||
|
function l(t) {
|
||||||
|
var a = n.exports.Promise,
|
||||||
|
i = void 0 !== a ? a : e.Promise
|
||||||
|
return 'function' == typeof i ? new i(t) : (t(r, r), null)
|
||||||
|
}
|
||||||
|
var c,
|
||||||
|
s,
|
||||||
|
u,
|
||||||
|
d,
|
||||||
|
f,
|
||||||
|
h,
|
||||||
|
g,
|
||||||
|
m,
|
||||||
|
b =
|
||||||
|
((u = Math.floor(1e3 / 60)),
|
||||||
|
(d = {}),
|
||||||
|
(f = 0),
|
||||||
|
'function' == typeof requestAnimationFrame &&
|
||||||
|
'function' == typeof cancelAnimationFrame
|
||||||
|
? ((c = function (t) {
|
||||||
|
var e = Math.random()
|
||||||
|
return (
|
||||||
|
(d[e] = requestAnimationFrame(function n(a) {
|
||||||
|
f === a || f + u - 1 < a
|
||||||
|
? ((f = a), delete d[e], t())
|
||||||
|
: (d[e] = requestAnimationFrame(n))
|
||||||
|
})),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
(s = function (t) {
|
||||||
|
d[t] && cancelAnimationFrame(d[t])
|
||||||
|
}))
|
||||||
|
: ((c = function (t) {
|
||||||
|
return setTimeout(t, u)
|
||||||
|
}),
|
||||||
|
(s = function (t) {
|
||||||
|
return clearTimeout(t)
|
||||||
|
})),
|
||||||
|
{frame: c, cancel: s}),
|
||||||
|
v =
|
||||||
|
((m = {}),
|
||||||
|
function () {
|
||||||
|
if (h) return h
|
||||||
|
if (!a && o) {
|
||||||
|
var e = [
|
||||||
|
'var CONFETTI, SIZE = {}, module = {};',
|
||||||
|
'(' + t.toString() + ')(this, module, true, SIZE);',
|
||||||
|
'onmessage = function(msg) {',
|
||||||
|
' if (msg.data.options) {',
|
||||||
|
' CONFETTI(msg.data.options).then(function () {',
|
||||||
|
' if (msg.data.callback) {',
|
||||||
|
' postMessage({ callback: msg.data.callback });',
|
||||||
|
' }',
|
||||||
|
' });',
|
||||||
|
' } else if (msg.data.reset) {',
|
||||||
|
' CONFETTI.reset();',
|
||||||
|
' } else if (msg.data.resize) {',
|
||||||
|
' SIZE.width = msg.data.resize.width;',
|
||||||
|
' SIZE.height = msg.data.resize.height;',
|
||||||
|
' } else if (msg.data.canvas) {',
|
||||||
|
' SIZE.width = msg.data.canvas.width;',
|
||||||
|
' SIZE.height = msg.data.canvas.height;',
|
||||||
|
' CONFETTI = module.exports.create(msg.data.canvas);',
|
||||||
|
' }',
|
||||||
|
'}'
|
||||||
|
].join('\n')
|
||||||
|
try {
|
||||||
|
h = new Worker(URL.createObjectURL(new Blob([e])))
|
||||||
|
} catch (t) {
|
||||||
|
return (
|
||||||
|
void 0 !== typeof console &&
|
||||||
|
'function' == typeof console.warn &&
|
||||||
|
console.warn('🎊 Could not load worker', t),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
!(function (t) {
|
||||||
|
function e(e, n) {
|
||||||
|
t.postMessage({options: e || {}, callback: n})
|
||||||
|
}
|
||||||
|
;(t.init = function (e) {
|
||||||
|
var n = e.transferControlToOffscreen()
|
||||||
|
t.postMessage({canvas: n}, [n])
|
||||||
|
}),
|
||||||
|
(t.fire = function (n, a, i) {
|
||||||
|
if (g) return e(n, null), g
|
||||||
|
var o = Math.random().toString(36).slice(2)
|
||||||
|
return (g = l(function (a) {
|
||||||
|
function r(e) {
|
||||||
|
e.data.callback === o &&
|
||||||
|
(delete m[o],
|
||||||
|
t.removeEventListener('message', r),
|
||||||
|
(g = null),
|
||||||
|
i(),
|
||||||
|
a())
|
||||||
|
}
|
||||||
|
t.addEventListener('message', r),
|
||||||
|
e(n, o),
|
||||||
|
(m[o] = r.bind(null, {data: {callback: o}}))
|
||||||
|
}))
|
||||||
|
}),
|
||||||
|
(t.reset = function () {
|
||||||
|
for (var e in (t.postMessage({reset: !0}), m))
|
||||||
|
m[e](), delete m[e]
|
||||||
|
})
|
||||||
|
})(h)
|
||||||
|
}
|
||||||
|
return h
|
||||||
|
}),
|
||||||
|
y = {
|
||||||
|
particleCount: 50,
|
||||||
|
angle: 90,
|
||||||
|
spread: 45,
|
||||||
|
startVelocity: 45,
|
||||||
|
decay: 0.9,
|
||||||
|
gravity: 1,
|
||||||
|
drift: 0,
|
||||||
|
ticks: 200,
|
||||||
|
x: 0.5,
|
||||||
|
y: 0.5,
|
||||||
|
shapes: ['square', 'circle'],
|
||||||
|
zIndex: 100,
|
||||||
|
colors: [
|
||||||
|
'#26ccff',
|
||||||
|
'#a25afd',
|
||||||
|
'#ff5e7e',
|
||||||
|
'#88ff5a',
|
||||||
|
'#fcff42',
|
||||||
|
'#ffa62d',
|
||||||
|
'#ff36ff'
|
||||||
|
],
|
||||||
|
disableForReducedMotion: !1,
|
||||||
|
scalar: 1
|
||||||
|
}
|
||||||
|
function p(t, e, n) {
|
||||||
|
return (function (t, e) {
|
||||||
|
return e ? e(t) : t
|
||||||
|
})(t && null != t[e] ? t[e] : y[e], n)
|
||||||
|
}
|
||||||
|
function M(t) {
|
||||||
|
return t < 0 ? 0 : Math.floor(t)
|
||||||
|
}
|
||||||
|
function w(t) {
|
||||||
|
return parseInt(t, 16)
|
||||||
|
}
|
||||||
|
function x(t) {
|
||||||
|
return t.map(C)
|
||||||
|
}
|
||||||
|
function C(t) {
|
||||||
|
var e = String(t).replace(/[^0-9a-f]/gi, '')
|
||||||
|
return (
|
||||||
|
e.length < 6 && (e = e[0] + e[0] + e[1] + e[1] + e[2] + e[2]),
|
||||||
|
{
|
||||||
|
r: w(e.substring(0, 2)),
|
||||||
|
g: w(e.substring(2, 4)),
|
||||||
|
b: w(e.substring(4, 6))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function k(t) {
|
||||||
|
;(t.width = document.documentElement.clientWidth),
|
||||||
|
(t.height = document.documentElement.clientHeight)
|
||||||
|
}
|
||||||
|
function I(t) {
|
||||||
|
var e = t.getBoundingClientRect()
|
||||||
|
;(t.width = e.width), (t.height = e.height)
|
||||||
|
}
|
||||||
|
function T(t, e, n, o, r) {
|
||||||
|
var c,
|
||||||
|
s,
|
||||||
|
u = e.slice(),
|
||||||
|
d = t.getContext('2d'),
|
||||||
|
f = l(function (e) {
|
||||||
|
function l() {
|
||||||
|
;(c = s = null), d.clearRect(0, 0, o.width, o.height), r(), e()
|
||||||
|
}
|
||||||
|
;(c = b.frame(function e() {
|
||||||
|
!a ||
|
||||||
|
(o.width === i.width && o.height === i.height) ||
|
||||||
|
((o.width = t.width = i.width), (o.height = t.height = i.height)),
|
||||||
|
o.width ||
|
||||||
|
o.height ||
|
||||||
|
(n(t), (o.width = t.width), (o.height = t.height)),
|
||||||
|
d.clearRect(0, 0, o.width, o.height),
|
||||||
|
(u = u.filter(function (t) {
|
||||||
|
return (function (t, e) {
|
||||||
|
;(e.x += Math.cos(e.angle2D) * e.velocity + e.drift),
|
||||||
|
(e.y += Math.sin(e.angle2D) * e.velocity + e.gravity),
|
||||||
|
(e.wobble += 0.1),
|
||||||
|
(e.velocity *= e.decay),
|
||||||
|
(e.tiltAngle += 0.1),
|
||||||
|
(e.tiltSin = Math.sin(e.tiltAngle)),
|
||||||
|
(e.tiltCos = Math.cos(e.tiltAngle)),
|
||||||
|
(e.random = Math.random() + 5),
|
||||||
|
(e.wobbleX = e.x + 10 * e.scalar * Math.cos(e.wobble)),
|
||||||
|
(e.wobbleY = e.y + 10 * e.scalar * Math.sin(e.wobble))
|
||||||
|
var n = e.tick++ / e.totalTicks,
|
||||||
|
a = e.x + e.random * e.tiltCos,
|
||||||
|
i = e.y + e.random * e.tiltSin,
|
||||||
|
o = e.wobbleX + e.random * e.tiltCos,
|
||||||
|
r = e.wobbleY + e.random * e.tiltSin
|
||||||
|
return (
|
||||||
|
(t.fillStyle =
|
||||||
|
'rgba(' +
|
||||||
|
e.color.r +
|
||||||
|
', ' +
|
||||||
|
e.color.g +
|
||||||
|
', ' +
|
||||||
|
e.color.b +
|
||||||
|
', ' +
|
||||||
|
(1 - n) +
|
||||||
|
')'),
|
||||||
|
t.beginPath(),
|
||||||
|
'circle' === e.shape
|
||||||
|
? t.ellipse
|
||||||
|
? t.ellipse(
|
||||||
|
e.x,
|
||||||
|
e.y,
|
||||||
|
Math.abs(o - a) * e.ovalScalar,
|
||||||
|
Math.abs(r - i) * e.ovalScalar,
|
||||||
|
(Math.PI / 10) * e.wobble,
|
||||||
|
0,
|
||||||
|
2 * Math.PI
|
||||||
|
)
|
||||||
|
: (function (t, e, n, a, i, o, r, l, c) {
|
||||||
|
t.save(),
|
||||||
|
t.translate(e, n),
|
||||||
|
t.rotate(o),
|
||||||
|
t.scale(a, i),
|
||||||
|
t.arc(0, 0, 1, r, l, c),
|
||||||
|
t.restore()
|
||||||
|
})(
|
||||||
|
t,
|
||||||
|
e.x,
|
||||||
|
e.y,
|
||||||
|
Math.abs(o - a) * e.ovalScalar,
|
||||||
|
Math.abs(r - i) * e.ovalScalar,
|
||||||
|
(Math.PI / 10) * e.wobble,
|
||||||
|
0,
|
||||||
|
2 * Math.PI
|
||||||
|
)
|
||||||
|
: (t.moveTo(Math.floor(e.x), Math.floor(e.y)),
|
||||||
|
t.lineTo(Math.floor(e.wobbleX), Math.floor(i)),
|
||||||
|
t.lineTo(Math.floor(o), Math.floor(r)),
|
||||||
|
t.lineTo(Math.floor(a), Math.floor(e.wobbleY))),
|
||||||
|
t.closePath(),
|
||||||
|
t.fill(),
|
||||||
|
e.tick < e.totalTicks
|
||||||
|
)
|
||||||
|
})(d, t)
|
||||||
|
})).length
|
||||||
|
? (c = b.frame(e))
|
||||||
|
: l()
|
||||||
|
})),
|
||||||
|
(s = l)
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
addFettis: function (t) {
|
||||||
|
return (u = u.concat(t)), f
|
||||||
|
},
|
||||||
|
canvas: t,
|
||||||
|
promise: f,
|
||||||
|
reset: function () {
|
||||||
|
c && b.cancel(c), s && s()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function E(t, n) {
|
||||||
|
var a,
|
||||||
|
i = !t,
|
||||||
|
r = !!p(n || {}, 'resize'),
|
||||||
|
c = p(n, 'disableForReducedMotion', Boolean),
|
||||||
|
s = o && !!p(n || {}, 'useWorker') ? v() : null,
|
||||||
|
u = i ? k : I,
|
||||||
|
d = !(!t || !s) && !!t.__confetti_initialized,
|
||||||
|
f =
|
||||||
|
'function' == typeof matchMedia &&
|
||||||
|
matchMedia('(prefers-reduced-motion)').matches
|
||||||
|
function h(e, n, i) {
|
||||||
|
for (
|
||||||
|
var o,
|
||||||
|
r,
|
||||||
|
l,
|
||||||
|
c,
|
||||||
|
s,
|
||||||
|
d = p(e, 'particleCount', M),
|
||||||
|
f = p(e, 'angle', Number),
|
||||||
|
h = p(e, 'spread', Number),
|
||||||
|
g = p(e, 'startVelocity', Number),
|
||||||
|
m = p(e, 'decay', Number),
|
||||||
|
b = p(e, 'gravity', Number),
|
||||||
|
v = p(e, 'drift', Number),
|
||||||
|
y = p(e, 'colors', x),
|
||||||
|
w = p(e, 'ticks', Number),
|
||||||
|
C = p(e, 'shapes'),
|
||||||
|
k = p(e, 'scalar'),
|
||||||
|
I = (function (t) {
|
||||||
|
var e = p(t, 'origin', Object)
|
||||||
|
return (e.x = p(e, 'x', Number)), (e.y = p(e, 'y', Number)), e
|
||||||
|
})(e),
|
||||||
|
E = d,
|
||||||
|
S = [],
|
||||||
|
F = t.width * I.x,
|
||||||
|
N = t.height * I.y;
|
||||||
|
E--;
|
||||||
|
|
||||||
|
)
|
||||||
|
S.push(
|
||||||
|
((o = {
|
||||||
|
x: F,
|
||||||
|
y: N,
|
||||||
|
angle: f,
|
||||||
|
spread: h,
|
||||||
|
startVelocity: g,
|
||||||
|
color: y[E % y.length],
|
||||||
|
shape:
|
||||||
|
C[
|
||||||
|
((c = 0),
|
||||||
|
(s = C.length),
|
||||||
|
Math.floor(Math.random() * (s - c)) + c)
|
||||||
|
],
|
||||||
|
ticks: w,
|
||||||
|
decay: m,
|
||||||
|
gravity: b,
|
||||||
|
drift: v,
|
||||||
|
scalar: k
|
||||||
|
}),
|
||||||
|
(r = void 0),
|
||||||
|
(l = void 0),
|
||||||
|
(r = o.angle * (Math.PI / 180)),
|
||||||
|
(l = o.spread * (Math.PI / 180)),
|
||||||
|
{
|
||||||
|
x: o.x,
|
||||||
|
y: o.y,
|
||||||
|
wobble: 10 * Math.random(),
|
||||||
|
velocity: 0.5 * o.startVelocity + Math.random() * o.startVelocity,
|
||||||
|
angle2D: -r + (0.5 * l - Math.random() * l),
|
||||||
|
tiltAngle: Math.random() * Math.PI,
|
||||||
|
color: o.color,
|
||||||
|
shape: o.shape,
|
||||||
|
tick: 0,
|
||||||
|
totalTicks: o.ticks,
|
||||||
|
decay: o.decay,
|
||||||
|
drift: o.drift,
|
||||||
|
random: Math.random() + 5,
|
||||||
|
tiltSin: 0,
|
||||||
|
tiltCos: 0,
|
||||||
|
wobbleX: 0,
|
||||||
|
wobbleY: 0,
|
||||||
|
gravity: 3 * o.gravity,
|
||||||
|
ovalScalar: 0.6,
|
||||||
|
scalar: o.scalar
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return a ? a.addFettis(S) : (a = T(t, S, u, n, i)).promise
|
||||||
|
}
|
||||||
|
function g(n) {
|
||||||
|
var o = c || p(n, 'disableForReducedMotion', Boolean),
|
||||||
|
g = p(n, 'zIndex', Number)
|
||||||
|
if (o && f)
|
||||||
|
return l(function (t) {
|
||||||
|
t()
|
||||||
|
})
|
||||||
|
i && a
|
||||||
|
? (t = a.canvas)
|
||||||
|
: i &&
|
||||||
|
!t &&
|
||||||
|
((t = (function (t) {
|
||||||
|
var e = document.createElement('canvas')
|
||||||
|
return (
|
||||||
|
(e.style.position = 'fixed'),
|
||||||
|
(e.style.top = '0px'),
|
||||||
|
(e.style.left = '0px'),
|
||||||
|
(e.style.pointerEvents = 'none'),
|
||||||
|
(e.style.zIndex = t),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})(g)),
|
||||||
|
document.body.appendChild(t)),
|
||||||
|
r && !d && u(t)
|
||||||
|
var m = {width: t.width, height: t.height}
|
||||||
|
function b() {
|
||||||
|
if (s) {
|
||||||
|
var e = {
|
||||||
|
getBoundingClientRect: function () {
|
||||||
|
if (!i) return t.getBoundingClientRect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
u(e),
|
||||||
|
void s.postMessage({resize: {width: e.width, height: e.height}})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
m.width = m.height = null
|
||||||
|
}
|
||||||
|
function v() {
|
||||||
|
;(a = null),
|
||||||
|
r && e.removeEventListener('resize', b),
|
||||||
|
i && t && (document.body.removeChild(t), (t = null), (d = !1))
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
s && !d && s.init(t),
|
||||||
|
(d = !0),
|
||||||
|
s && (t.__confetti_initialized = !0),
|
||||||
|
r && e.addEventListener('resize', b, !1),
|
||||||
|
s ? s.fire(n, m, v) : h(n, m, v)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
(g.reset = function () {
|
||||||
|
s && s.reset(), a && a.reset()
|
||||||
|
}),
|
||||||
|
g
|
||||||
|
)
|
||||||
|
}
|
||||||
|
;(n.exports = E(null, {useWorker: !0, resize: !0})), (n.exports.create = E)
|
||||||
|
})(
|
||||||
|
(function () {
|
||||||
|
return void 0 !== t ? t : 'undefined' != typeof self ? self : this || {}
|
||||||
|
})(),
|
||||||
|
e,
|
||||||
|
!1
|
||||||
|
),
|
||||||
|
(t.confetti = e.exports)
|
||||||
|
})(window, {})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// update cache version every time there is a new deployment
|
// update cache version every time there is a new deployment
|
||||||
// so the service worker reinitializes the cache
|
// so the service worker reinitializes the cache
|
||||||
const CACHE_VERSION = 107
|
const CACHE_VERSION = 117
|
||||||
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
const CURRENT_CACHE = `lnbits-${CACHE_VERSION}-`
|
||||||
|
|
||||||
const getApiKey = request => {
|
const getApiKey = request => {
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ new Vue({
|
|||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
origin: window.location.origin,
|
||||||
user: LNbits.map.user(window.user),
|
user: LNbits.map.user(window.user),
|
||||||
receive: {
|
receive: {
|
||||||
show: false,
|
show: false,
|
||||||
@@ -860,11 +861,11 @@ new Vue({
|
|||||||
this.disclaimerDialog.show = true
|
this.disclaimerDialog.show = true
|
||||||
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// listen to incoming payments
|
// listen to incoming payments
|
||||||
LNbits.events.onInvoicePaid(this.g.wallet, payment =>
|
LNbits.events.onInvoicePaid(this.g.wallet, payment =>
|
||||||
this.onPaymentReceived(payment.payment_hash)
|
this.onPaymentReceived(payment.payment_hash)
|
||||||
)
|
)
|
||||||
|
eventReactionWebocket(wallet.id)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -227,3 +227,7 @@ video {
|
|||||||
padding: 0.2rem;
|
padding: 0.2rem;
|
||||||
border-radius: 0.2rem;
|
border-radius: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.whitespace-pre-line {
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"js/components.js",
|
"js/components.js",
|
||||||
"js/components/lnbits-funding-sources.js",
|
"js/components/lnbits-funding-sources.js",
|
||||||
"js/components/extension-settings.js",
|
"js/components/extension-settings.js",
|
||||||
|
"js/event-reactions.js",
|
||||||
"js/bolt11-decoder.js"
|
"js/bolt11-decoder.js"
|
||||||
],
|
],
|
||||||
"css": ["vendor/quasar.css", "vendor/Chart.css", "css/base.css"]
|
"css": ["vendor/quasar.css", "vendor/Chart.css", "css/base.css"]
|
||||||
|
|||||||
+17
-42
@@ -14,11 +14,9 @@ from pywebpush import WebPushException, webpush
|
|||||||
from lnbits.core.crud import (
|
from lnbits.core.crud import (
|
||||||
delete_expired_invoices,
|
delete_expired_invoices,
|
||||||
delete_webpush_subscriptions,
|
delete_webpush_subscriptions,
|
||||||
get_balance_checks,
|
|
||||||
get_payments,
|
get_payments,
|
||||||
get_standalone_payment,
|
get_standalone_payment,
|
||||||
)
|
)
|
||||||
from lnbits.core.services import redeem_lnurl_withdraw
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.wallets import get_wallet_class
|
from lnbits.wallets import get_wallet_class
|
||||||
|
|
||||||
@@ -60,42 +58,25 @@ async def send_push_promise(a, b) -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SseListenersDict(dict):
|
invoice_listeners: Dict[str, asyncio.Queue] = {}
|
||||||
"""
|
|
||||||
A dict of sse listeners.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name: Optional[str] = None):
|
|
||||||
self.name = name or f"sse_listener_{str(uuid.uuid4())[:8]}"
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
assert isinstance(key, str), f"{key} is not a string"
|
|
||||||
assert isinstance(value, asyncio.Queue), f"{value} is not an asyncio.Queue"
|
|
||||||
logger.trace(f"sse: adding listener {key} to {self.name}. len = {len(self)+1}")
|
|
||||||
return super().__setitem__(key, value)
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}")
|
|
||||||
return super().__delitem__(key)
|
|
||||||
|
|
||||||
_RaiseKeyError = object() # singleton for no-default behavior
|
|
||||||
|
|
||||||
def pop(self, key, v=_RaiseKeyError) -> None:
|
|
||||||
logger.trace(f"sse: removing listener from {self.name}. len = {len(self)-1}")
|
|
||||||
return super().pop(key)
|
|
||||||
|
|
||||||
|
|
||||||
invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict("invoice_listeners")
|
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: name should not be optional
|
||||||
|
# some extensions still dont use a name, but they should
|
||||||
def register_invoice_listener(send_chan: asyncio.Queue, name: Optional[str] = None):
|
def register_invoice_listener(send_chan: asyncio.Queue, name: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
A method intended for extensions (and core/tasks.py) to call when they want to be
|
A method intended for extensions (and core/tasks.py) to call when they want to be
|
||||||
notified about new invoice payments incoming. Will emit all incoming payments.
|
notified about new invoice payments incoming. Will emit all incoming payments.
|
||||||
"""
|
"""
|
||||||
name_unique = f"{name or 'no_name'}_{str(uuid.uuid4())[:8]}"
|
if not name:
|
||||||
logger.trace(f"sse: registering invoice listener {name_unique}")
|
# fallback to a random name if extension didn't provide one
|
||||||
invoice_listeners[name_unique] = send_chan
|
name = f"no_name_{str(uuid.uuid4())[:8]}"
|
||||||
|
|
||||||
|
if invoice_listeners.get(name):
|
||||||
|
logger.warning(f"invoice listener `{name}` already exists, replacing it")
|
||||||
|
|
||||||
|
logger.trace(f"registering invoice listener `{name}`")
|
||||||
|
invoice_listeners[name] = send_chan
|
||||||
|
|
||||||
|
|
||||||
async def webhook_handler():
|
async def webhook_handler():
|
||||||
@@ -186,14 +167,6 @@ async def check_pending_payments():
|
|||||||
await asyncio.sleep(60 * 30) # every 30 minutes
|
await asyncio.sleep(60 * 30) # every 30 minutes
|
||||||
|
|
||||||
|
|
||||||
async def perform_balance_checks():
|
|
||||||
while True:
|
|
||||||
for bc in await get_balance_checks():
|
|
||||||
await redeem_lnurl_withdraw(bc.wallet, bc.url)
|
|
||||||
|
|
||||||
await asyncio.sleep(60 * 60 * 6) # every 6 hours
|
|
||||||
|
|
||||||
|
|
||||||
async def invoice_callback_dispatcher(checking_id: str):
|
async def invoice_callback_dispatcher(checking_id: str):
|
||||||
"""
|
"""
|
||||||
Takes incoming payments, sets pending=False, and dispatches them to
|
Takes incoming payments, sets pending=False, and dispatches them to
|
||||||
@@ -201,10 +174,12 @@ async def invoice_callback_dispatcher(checking_id: str):
|
|||||||
"""
|
"""
|
||||||
payment = await get_standalone_payment(checking_id, incoming=True)
|
payment = await get_standalone_payment(checking_id, incoming=True)
|
||||||
if payment and payment.is_in:
|
if payment and payment.is_in:
|
||||||
logger.trace(f"sse sending invoice callback for payment {checking_id}")
|
logger.trace(
|
||||||
|
f"invoice listeners: sending invoice callback for payment {checking_id}"
|
||||||
|
)
|
||||||
await payment.set_pending(False)
|
await payment.set_pending(False)
|
||||||
for chan_name, send_chan in invoice_listeners.items():
|
for name, send_chan in invoice_listeners.items():
|
||||||
logger.trace(f"sse sending to chan: {chan_name}")
|
logger.trace(f"invoice listeners: sending to `{name}`")
|
||||||
await send_chan.put(payment)
|
await send_chan.put(payment)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Generated
+345
-755
File diff suppressed because it is too large
Load Diff
@@ -86,6 +86,7 @@
|
|||||||
"js/components.js",
|
"js/components.js",
|
||||||
"js/components/lnbits-funding-sources.js",
|
"js/components/lnbits-funding-sources.js",
|
||||||
"js/components/extension-settings.js",
|
"js/components/extension-settings.js",
|
||||||
|
"js/event-reactions.js",
|
||||||
"js/bolt11-decoder.js"
|
"js/bolt11-decoder.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
|
|||||||
Generated
+1
-1
@@ -2862,4 +2862,4 @@ liquid = ["wallycore"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10 | ^3.9"
|
python-versions = "^3.10 | ^3.9"
|
||||||
content-hash = "c29cac992fae552eb8da791e13fe7766eab7571e260a42cc0de0d755508c42b7"
|
content-hash = "b2e21e0075047150888581a401d46fbe8efd6226e85a85189c3f3db51f825a48"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "0.12.0"
|
version = "0.12.2"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||||
|
|
||||||
|
|||||||
+3
-3
@@ -36,7 +36,7 @@ is_regtest: bool = not is_fake
|
|||||||
docker_lightning_cli = [
|
docker_lightning_cli = [
|
||||||
"docker",
|
"docker",
|
||||||
"exec",
|
"exec",
|
||||||
"lnbits-legend-lnd-1-1",
|
"lnbits-lnd-1-1",
|
||||||
"lncli",
|
"lncli",
|
||||||
"--network",
|
"--network",
|
||||||
"regtest",
|
"regtest",
|
||||||
@@ -46,7 +46,7 @@ docker_lightning_cli = [
|
|||||||
docker_bitcoin_cli = [
|
docker_bitcoin_cli = [
|
||||||
"docker",
|
"docker",
|
||||||
"exec",
|
"exec",
|
||||||
"lnbits-legend-bitcoind-1-1" "bitcoin-cli",
|
"lnbits-bitcoind-1-1" "bitcoin-cli",
|
||||||
"-rpcuser=lnbits",
|
"-rpcuser=lnbits",
|
||||||
"-rpcpassword=lnbits",
|
"-rpcpassword=lnbits",
|
||||||
"-regtest",
|
"-regtest",
|
||||||
@@ -56,7 +56,7 @@ docker_bitcoin_cli = [
|
|||||||
docker_lightning_unconnected_cli = [
|
docker_lightning_unconnected_cli = [
|
||||||
"docker",
|
"docker",
|
||||||
"exec",
|
"exec",
|
||||||
"lnbits-legend-lnd-2-1",
|
"lnbits-lnd-2-1",
|
||||||
"lncli",
|
"lncli",
|
||||||
"--network",
|
"--network",
|
||||||
"regtest",
|
"regtest",
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ def translate_string(lang_from, lang_to, text):
|
|||||||
"cs": "Czech",
|
"cs": "Czech",
|
||||||
"sk": "Slovak",
|
"sk": "Slovak",
|
||||||
"kr": "Korean",
|
"kr": "Korean",
|
||||||
|
"fi": "Finnish",
|
||||||
}[lang_to]
|
}[lang_to]
|
||||||
assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY env var not set"
|
assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY env var not set"
|
||||||
client = OpenAI()
|
client = OpenAI()
|
||||||
|
|||||||
Reference in New Issue
Block a user