Compare commits
68
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2329770acd | ||
|
|
7d98c6b080 | ||
|
|
5f4e889d3a | ||
|
|
fe774ae1a1 | ||
|
|
21e02cb20e | ||
|
|
a3d7b463ae | ||
|
|
e1e2112461 | ||
|
|
92fc9a130f | ||
|
|
e15f3cd207 | ||
|
|
041b358310 | ||
|
|
cbd3de88c4 | ||
|
|
156ab10ad6 | ||
|
|
b77c103020 | ||
|
|
1f34357ab7 | ||
|
|
d9eda48298 | ||
|
|
1be2d1a680 | ||
|
|
b55ae9daaf | ||
|
|
1f98aa2cdb | ||
|
|
55ec3f1812 | ||
|
|
7880044483 | ||
|
|
28b3f79d9b | ||
|
|
4c704305ac | ||
|
|
c47fd2a4d1 | ||
|
|
1255df43e6 | ||
|
|
6ccc10d327 | ||
|
|
8bcf5e4f70 | ||
|
|
04ec643c93 | ||
|
|
f93efbfb3a | ||
|
|
4429c0cdb6 | ||
|
|
2bdb89b4b9 | ||
|
|
75754598e1 | ||
|
|
b25a3c2bb4 | ||
|
|
e7c4de6506 | ||
|
|
c102f10fab | ||
|
|
7c28b58aaa | ||
|
|
2b27f13fa8 | ||
|
|
130f4fd0bc | ||
|
|
4894b70bad | ||
|
|
05769240d3 | ||
|
|
7bd127d22c | ||
|
|
6dc3df148d | ||
|
|
e286546b63 | ||
|
|
eae1be1db8 | ||
|
|
25ab676a3f | ||
|
|
2fd6648a9a | ||
|
|
8eda653e3f | ||
|
|
7e327b2c6a | ||
|
|
40c055e373 | ||
|
|
1d9808b269 | ||
|
|
c65ab2af87 | ||
|
|
15f093c136 | ||
|
|
5311b0a393 | ||
|
|
e72396fe31 | ||
|
|
d6d0ebb20e | ||
|
|
563dd5b499 | ||
|
|
9be894da74 | ||
|
|
87b66c554a | ||
|
|
314b533e39 | ||
|
|
af8f3382c1 | ||
|
|
67bb86c1ce | ||
|
|
f0f9e41f99 | ||
|
|
2fbaf57b5e | ||
|
|
87afba2abb | ||
|
|
c375bf1b15 | ||
|
|
dbcc920d0a | ||
|
|
2c61e219b8 | ||
|
|
6cb9b7da1f | ||
|
|
83334d67c8 |
+17
-4
@@ -6,6 +6,10 @@
|
|||||||
# The following settings are ONLY set in your .env file.
|
# The following settings are ONLY set in your .env file.
|
||||||
# They are NOT managed by the Admin UI and are not stored in the database.
|
# They are NOT managed by the Admin UI and are not stored in the database.
|
||||||
|
|
||||||
|
# === First Install Token ===
|
||||||
|
# if set the user is required to enter this token on the /first_install page
|
||||||
|
# FIRST_INSTALL_TOKEN="myaccesstoken"
|
||||||
|
|
||||||
# === Logging and Development ===
|
# === Logging and Development ===
|
||||||
|
|
||||||
DEBUG=False
|
DEBUG=False
|
||||||
@@ -55,7 +59,7 @@ LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos"
|
|||||||
# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx
|
# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx
|
||||||
|
|
||||||
# which fundingsources are allowed in the admin ui
|
# which fundingsources are allowed in the admin ui
|
||||||
# LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, BlinkWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet, NWCWallet, BreezSdkWallet, BoltzWallet, StrikeWallet, CLNRestWallet, SparkWallet, LightsparkSparkWallet"
|
# LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, BlinkWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet, NWCWallet, BreezSdkWallet, BoltzWallet, StrikeWallet, CLNRestWallet, SparkWallet, SparkL2Wallet"
|
||||||
|
|
||||||
# uvicorn variable, allow https behind a proxy
|
# uvicorn variable, allow https behind a proxy
|
||||||
# IMPORTANT: this also needs the webserver to be configured to forward the headers
|
# IMPORTANT: this also needs the webserver to be configured to forward the headers
|
||||||
@@ -187,7 +191,14 @@ BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon"
|
|||||||
# HEXSTRING instead of path also possible
|
# HEXSTRING instead of path also possible
|
||||||
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert"
|
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert"
|
||||||
|
|
||||||
# TODO: add Spark
|
# SparkL2Wallet (external sidecar: https://github.com/lnbits/spark_sidecar)
|
||||||
|
SPARK_L2_NETWORK=MAINNET
|
||||||
|
SPARK_L2_EXTERNAL_ENDPOINT=http://127.0.0.1:8765
|
||||||
|
SPARK_L2_EXTERNAL_API_KEY=
|
||||||
|
# optional tuning
|
||||||
|
# SPARK_L2_PAY_WAIT_MS=4000
|
||||||
|
# SPARK_L2_PAY_POLL_MS=500
|
||||||
|
# SPARK_L2_STREAM_KEEPALIVE_MS=15000
|
||||||
|
|
||||||
# StrikeWallet
|
# StrikeWallet
|
||||||
STRIKE_API_ENDPOINT=https://api.strike.me/v1
|
STRIKE_API_ENDPOINT=https://api.strike.me/v1
|
||||||
@@ -315,10 +326,12 @@ LNBITS_SERVICE_FEE=0.0
|
|||||||
# disable fees for internal transactions
|
# disable fees for internal transactions
|
||||||
# LNBITS_SERVICE_FEE_IGNORE_INTERNAL=true
|
# LNBITS_SERVICE_FEE_IGNORE_INTERNAL=true
|
||||||
|
|
||||||
# value in millisats
|
# The minimum fee reserved per payment (millisats)
|
||||||
LNBITS_RESERVE_FEE_MIN=2000
|
LNBITS_RESERVE_FEE_MIN=2000
|
||||||
# value in percent
|
# The percentage of the payment amount to reserve for routing fees (percent)
|
||||||
LNBITS_RESERVE_FEE_PERCENT=1.0
|
LNBITS_RESERVE_FEE_PERCENT=1.0
|
||||||
|
# The default time to wait to for status response from the funding source when paying an invoice
|
||||||
|
LNBITS_FUNDING_SOURCE_PAY_INVOICE_WAIT_SECONDS=5
|
||||||
|
|
||||||
# limit the maximum balance for each wallet
|
# limit the maximum balance for each wallet
|
||||||
# throw an error if the wallet attempts to create a new invoice
|
# throw an error if the wallet attempts to create a new invoice
|
||||||
|
|||||||
@@ -55,11 +55,21 @@ jobs:
|
|||||||
- name: Build and push boltz
|
- name: Build and push boltz
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
context: .
|
context: docker/boltzclient
|
||||||
file: Dockerfile.boltz
|
|
||||||
push: true
|
push: true
|
||||||
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits-boltz:${{ inputs.tag }}
|
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits-boltz:${{ 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
|
||||||
build-args: LNBITS_TAG=${{ inputs.tag }}
|
build-args: LNBITS_TAG=${{ inputs.tag }}
|
||||||
|
|
||||||
|
- name: Build and push sparkl2
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: docker/sparkl2
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits-sparkl2:${{ inputs.tag }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||||
|
build-args: LNBITS_TAG=${{ inputs.tag }}
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup Regtest
|
- name: Setup Regtest
|
||||||
run: |
|
run: |
|
||||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
cd docker/regtest
|
||||||
cd docker
|
|
||||||
chmod +x ./start-regtest
|
chmod +x ./start-regtest
|
||||||
./start-regtest
|
./start-regtest
|
||||||
sudo chmod -R a+rwx .
|
sudo chmod -R a+rwx .
|
||||||
@@ -50,16 +49,16 @@ jobs:
|
|||||||
LNBITS_DATABASE_URL: ${{ inputs.db-url }}
|
LNBITS_DATABASE_URL: ${{ inputs.db-url }}
|
||||||
LNBITS_BACKEND_WALLET_CLASS: ${{ inputs.backend-wallet-class }}
|
LNBITS_BACKEND_WALLET_CLASS: ${{ inputs.backend-wallet-class }}
|
||||||
LND_REST_ENDPOINT: https://localhost:8081/
|
LND_REST_ENDPOINT: https://localhost:8081/
|
||||||
LND_REST_CERT: ./docker/data/lnd-3/tls.cert
|
LND_REST_CERT: ./docker/regtest/data/lnd-3/tls.cert
|
||||||
LND_REST_MACAROON: ./docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
LND_REST_MACAROON: ./docker/regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||||
LND_GRPC_ENDPOINT: localhost
|
LND_GRPC_ENDPOINT: localhost
|
||||||
LND_GRPC_PORT: 10009
|
LND_GRPC_PORT: 10009
|
||||||
LND_GRPC_CERT: docker/data/lnd-3/tls.cert
|
LND_GRPC_CERT: ./docker/regtest/data/lnd-3/tls.cert
|
||||||
LND_GRPC_MACAROON: docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
LND_GRPC_MACAROON: ./docker/regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||||
CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
|
CORELIGHTNING_RPC: ./docker/regtest/data/clightning-1/regtest/lightning-rpc
|
||||||
CORELIGHTNING_REST_URL: https://localhost:3001
|
CORELIGHTNING_REST_URL: https://localhost:3001
|
||||||
CORELIGHTNING_REST_MACAROON: ./docker/data/clightning-2-rest/access.macaroon
|
CORELIGHTNING_REST_MACAROON: ./docker/regtest/data/clightning-2-rest/access.macaroon
|
||||||
CORELIGHTNING_REST_CERT: ./docker/data/clightning-2-rest/certificate.pem
|
CORELIGHTNING_REST_CERT: ./docker/regtest/data/clightning-2-rest/certificate.pem
|
||||||
LNBITS_ENDPOINT: http://localhost:5001
|
LNBITS_ENDPOINT: http://localhost:5001
|
||||||
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
|
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
|
||||||
ECLAIR_URL: http://127.0.0.1:8082
|
ECLAIR_URL: http://127.0.0.1:8082
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ jobs:
|
|||||||
|
|
||||||
docker:
|
docker:
|
||||||
if: github.repository == 'lnbits/lnbits'
|
if: github.repository == 'lnbits/lnbits'
|
||||||
|
needs: [ release ]
|
||||||
uses: ./.github/workflows/docker.yml
|
uses: ./.github/workflows/docker.yml
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.ref_name }}
|
tag: ${{ github.ref_name }}
|
||||||
@@ -40,19 +41,33 @@ jobs:
|
|||||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
docker-latest-rc:
|
||||||
|
if: github.repository == 'lnbits/lnbits'
|
||||||
|
needs: [ release ]
|
||||||
|
uses: ./.github/workflows/docker.yml
|
||||||
|
with:
|
||||||
|
tag: latest-rc
|
||||||
|
secrets:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
pypi:
|
pypi:
|
||||||
if: github.repository == 'lnbits/lnbits'
|
if: github.repository == 'lnbits/lnbits'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies for building secp256k1
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y build-essential automake libtool libffi-dev libgmp-dev
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build and publish to pypi
|
- name: Set up Python 3.10
|
||||||
uses: JRubics/poetry-publish@v1.15
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
python-version: "3.10"
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
- name: Build the project
|
||||||
|
run: uv build
|
||||||
|
- name: Publish to pypi
|
||||||
|
env:
|
||||||
|
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_KEY }}
|
||||||
|
run: uv publish
|
||||||
|
|
||||||
appimage:
|
appimage:
|
||||||
needs: [ release ]
|
needs: [ release ]
|
||||||
|
|||||||
@@ -52,19 +52,33 @@ jobs:
|
|||||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
docker-latest-rc:
|
||||||
|
if: github.repository == 'lnbits/lnbits'
|
||||||
|
needs: [ release ]
|
||||||
|
uses: ./.github/workflows/docker.yml
|
||||||
|
with:
|
||||||
|
tag: latest-rc
|
||||||
|
secrets:
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
pypi:
|
pypi:
|
||||||
if: github.repository == 'lnbits/lnbits'
|
if: github.repository == 'lnbits/lnbits'
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Install dependencies for building secp256k1
|
|
||||||
run: |
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y build-essential automake libtool libffi-dev libgmp-dev
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Build and publish to pypi
|
- name: Set up Python 3.10
|
||||||
uses: JRubics/poetry-publish@v1.15
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
python-version: "3.10"
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v6
|
||||||
|
- name: Build the project
|
||||||
|
run: uv build
|
||||||
|
- name: Publish to pypi
|
||||||
|
env:
|
||||||
|
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_KEY }}
|
||||||
|
run: uv publish
|
||||||
|
|
||||||
appimage:
|
appimage:
|
||||||
needs: [ release ]
|
needs: [ release ]
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ lnbits/static/bundle.min.js.old
|
|||||||
lnbits/static/bundle.min.css.old
|
lnbits/static/bundle.min.css.old
|
||||||
lnbits/static/bundle-components.min.js.old
|
lnbits/static/bundle-components.min.js.old
|
||||||
lnbits/upgrades
|
lnbits/upgrades
|
||||||
docker
|
|
||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
*result*
|
*result*
|
||||||
|
|||||||
@@ -14,11 +14,13 @@ ENV PATH="/root/.local/bin:$PATH"
|
|||||||
# Reinstall dependencies just in case (needed for CMD usage)
|
# Reinstall dependencies just in case (needed for CMD usage)
|
||||||
RUN uv sync --all-extras
|
RUN uv sync --all-extras
|
||||||
|
|
||||||
# LNbits + boltzd configuration
|
# LNbits
|
||||||
ENV LNBITS_PORT="5000"
|
ENV LNBITS_PORT="5000"
|
||||||
ENV LNBITS_HOST="0.0.0.0"
|
ENV LNBITS_HOST="0.0.0.0"
|
||||||
ENV LNBITS_BACKEND_WALLET_CLASS="BoltzWallet"
|
ENV LNBITS_BACKEND_WALLET_CLASS="BoltzWallet"
|
||||||
ENV FUNDING_SOURCE_MAX_RETRIES=10
|
ENV FUNDING_SOURCE_MAX_RETRIES="10"
|
||||||
|
|
||||||
|
# Boltzd
|
||||||
ENV BOLTZ_CLIENT_ENDPOINT="127.0.0.1:9002"
|
ENV BOLTZ_CLIENT_ENDPOINT="127.0.0.1:9002"
|
||||||
ENV BOLTZ_CLIENT_MACAROON="/root/.boltz/macaroons/admin.macaroon"
|
ENV BOLTZ_CLIENT_MACAROON="/root/.boltz/macaroons/admin.macaroon"
|
||||||
ENV BOLTZ_CLIENT_CERT="/root/.boltz/tls.cert"
|
ENV BOLTZ_CLIENT_CERT="/root/.boltz/tls.cert"
|
||||||
@@ -26,8 +28,7 @@ ENV BOLTZ_CLIENT_WALLET="lnbits"
|
|||||||
|
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
# Entrypoint to start boltzd and LNbits
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
COPY dockerboltz.sh /dockerboltz.sh
|
RUN chmod +x /entrypoint.sh
|
||||||
RUN chmod +x /dockerboltz.sh
|
|
||||||
|
|
||||||
CMD ["/dockerboltz.sh"]
|
CMD ["/entrypoint.sh"]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
!data
|
||||||
|
data/*
|
||||||
|
|
||||||
|
!data/boltz
|
||||||
|
data/boltz/*
|
||||||
|
!data/boltz/boltz.conf
|
||||||
|
|
||||||
|
!data/boltz-client
|
||||||
|
data/boltz-client/*
|
||||||
|
!data/boltz-client/boltz.toml
|
||||||
|
|
||||||
|
!data/boltz-nginx
|
||||||
|
data/boltz-nginx/*
|
||||||
|
!data/boltz-nginx/default.conf
|
||||||
|
|
||||||
|
!data/eclair
|
||||||
|
data/eclair/*
|
||||||
|
!data/eclair/eclair.conf
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|

|
||||||
|
|
||||||
|
# nodes
|
||||||
|
|
||||||
|
- lnd-1: for locally testing your current lnbits
|
||||||
|
- lnd-2: used for boltz backend
|
||||||
|
- lnd-3: used for lnbits inside docker
|
||||||
|
- cln-1: for locally testing your current lnbits
|
||||||
|
- cln-2: used for clightning-REST
|
||||||
|
- eclair-1: for locally testing your current lnbits
|
||||||
|
|
||||||
|
# Installing regtest
|
||||||
|
|
||||||
|
get the regtest enviroment ready
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Install docker https://docs.docker.com/engine/install/
|
||||||
|
# Make sure your user has permission to use docker 'sudo usermod -aG docker ${USER}' then reboot
|
||||||
|
# Stop/start docker 'sudo systemctl stop docker' 'sudo systemctl start docker'
|
||||||
|
|
||||||
|
sudo apt install jq
|
||||||
|
git clone https://github.com/lnbits/lnbits.git
|
||||||
|
cd lnbits
|
||||||
|
docker build -t lnbits/lnbits .
|
||||||
|
mkdir docker
|
||||||
|
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||||
|
cd docker
|
||||||
|
chmod +x ./start-regtest
|
||||||
|
./start-regtest # start the regtest and also run tests
|
||||||
|
sudo chown -R $USER ./data # Give the data file permissions for user
|
||||||
|
```
|
||||||
|
|
||||||
|
# Running LNbits on regtest
|
||||||
|
|
||||||
|
add this ENV variables to your `.env` file
|
||||||
|
|
||||||
|
```sh
|
||||||
|
DEBUG=true
|
||||||
|
|
||||||
|
# LND
|
||||||
|
LNBITS_BACKEND_WALLET_CLASS="LndRestWallet"
|
||||||
|
LND_REST_ENDPOINT=https://127.0.0.1:8081/
|
||||||
|
LND_REST_CERT=/home/user/repos/lnbits/docker/data/lnd-1/tls.cert
|
||||||
|
LND_REST_MACAROON=/home/user/repos/lnbits/docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
|
||||||
|
|
||||||
|
# CLN
|
||||||
|
LNBITS_BACKEND_WALLET_CLASS="CoreLightningWallet"
|
||||||
|
CORELIGHTNING_RPC=./docker/data/clightning-1/regtest/lightning-rpc
|
||||||
|
|
||||||
|
|
||||||
|
# Run LNbits
|
||||||
|
uv run lnbits
|
||||||
|
|
||||||
|
# Run LNbits with hot reload
|
||||||
|
make dev
|
||||||
|
```
|
||||||
|
|
||||||
|
# testing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod +x ./start-regtest
|
||||||
|
./start-regtest
|
||||||
|
# short answer :)
|
||||||
|
./start-regtest && echo "PASSED" || echo "FAILED" > /dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
usage of the `bitcoin-cli-sim`, `lightning-cli-sim` and `lncli-sim` aliases
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd ~/lnbits/docker
|
||||||
|
source docker-scripts.sh
|
||||||
|
# use bitcoin core, mine a block
|
||||||
|
bitcoin-cli-sim -generate 1
|
||||||
|
|
||||||
|
# use c-lightning nodes
|
||||||
|
lightning-cli-sim 1 newaddr | jq -r '.bech32' # use node 1
|
||||||
|
lightning-cli-sim 2 getinfo # use node 2
|
||||||
|
lightning-cli-sim 3 getinfo # use node 3
|
||||||
|
|
||||||
|
# use lnd nodes
|
||||||
|
lncli-sim 1 newaddr p2wsh
|
||||||
|
lncli-sim 2 listpeers
|
||||||
|
```
|
||||||
|
|
||||||
|
# urls
|
||||||
|
|
||||||
|
- mempool: http://localhost:8080/
|
||||||
|
- boltz api: http://localhost:9001/
|
||||||
|
- lnd-1 rest: http://localhost:8081/
|
||||||
|
- lnbits: http://localhost:5001/
|
||||||
|
|
||||||
|
# debugging docker logs
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker logs lnbits-lnbits-1 -f
|
||||||
|
docker logs lnbits-boltz-1 -f
|
||||||
|
docker logs lnbits-clightning-1-1 -f
|
||||||
|
docker logs lnbits-lnd-2-1 -f
|
||||||
|
```
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
standalone = true
|
||||||
|
network = "regtest"
|
||||||
|
|
||||||
|
# Path the the log file
|
||||||
|
logfile = ""
|
||||||
|
|
||||||
|
electrumUrl = "electrs:19001"
|
||||||
|
electrumLiquidUrl = "electrs-liquid:19002"
|
||||||
|
|
||||||
|
[BOLTZ]
|
||||||
|
# By default the daemon automatically connects to the official Boltz instance for the network LND is on
|
||||||
|
# This value is used to override that
|
||||||
|
url = "http://boltz-nginx:9001"
|
||||||
|
|
||||||
|
[DATABASE]
|
||||||
|
# Path to the SQLite database file
|
||||||
|
# path = "/home/michael/test.db"
|
||||||
|
|
||||||
|
[RPC]
|
||||||
|
# Host of
|
||||||
|
host = "0.0.0.0"
|
||||||
|
|
||||||
|
# Port of the gRPC interface
|
||||||
|
port = 9002
|
||||||
|
|
||||||
|
# Whether the REST proxy for the gRPC interface should be disabled
|
||||||
|
restDisabled = false
|
||||||
|
|
||||||
|
# Host of the REST proxy
|
||||||
|
restHost = "0.0.0.0"
|
||||||
|
|
||||||
|
# Port of the REST proxy
|
||||||
|
restPort = 9003
|
||||||
|
|
||||||
|
# Path to the TLS cert for the gRPC and REST interface
|
||||||
|
tlsCert = ""
|
||||||
|
|
||||||
|
# Path to the TLS private key for the gRPC and REST interface
|
||||||
|
tlsKey = ""
|
||||||
|
noTls = true
|
||||||
|
|
||||||
|
# Whether the macaroon authentication for the gRPC and REST interface should be disabled
|
||||||
|
noMacaroons = true
|
||||||
|
|
||||||
|
# Path to the admin macaroon for the gRPC and REST interface
|
||||||
|
adminMacaroonPath = ""
|
||||||
|
|
||||||
|
# Path to the read only macaroon for the gRPC and REST interface
|
||||||
|
readOnlyMacaroonPath = ""
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
upstream boltz {
|
||||||
|
server boltz:9001;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream boltzr {
|
||||||
|
server boltz:9005;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream ws {
|
||||||
|
server boltz:9004;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 9001;
|
||||||
|
listen [::]:9001;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
add_header Access-Control-Allow-Origin "*" always;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, PATCH, DELETE, POST, OPTIONS' always;
|
||||||
|
add_header Access-Control-Allow-Headers "*" always;
|
||||||
|
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
|
if ($request_method = OPTIONS) {
|
||||||
|
return 204;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /v2/ws {
|
||||||
|
proxy_pass http://ws/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/v2/(lightning|swap/rescue|swap/restore) {
|
||||||
|
proxy_pass http://boltzr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /streamswapstatus {
|
||||||
|
proxy_pass http://boltzr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://boltz;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
[api]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 9_001
|
||||||
|
|
||||||
|
[grpc]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 9_000
|
||||||
|
|
||||||
|
[postgres]
|
||||||
|
host = "boltz-postgres"
|
||||||
|
port = 5432
|
||||||
|
database = "boltz"
|
||||||
|
username = "boltz"
|
||||||
|
password = "boltz"
|
||||||
|
|
||||||
|
[sidecar]
|
||||||
|
[sidecar.grpc]
|
||||||
|
host = "127.0.0.1"
|
||||||
|
port = 9003
|
||||||
|
|
||||||
|
[sidecar.ws]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 9004
|
||||||
|
|
||||||
|
[sidecar.api]
|
||||||
|
host = "0.0.0.0"
|
||||||
|
port = 9005
|
||||||
|
|
||||||
|
[swap]
|
||||||
|
deferredClaimSymbols = ["BTC", "L-BTC"]
|
||||||
|
|
||||||
|
[[pairs]]
|
||||||
|
base = "BTC"
|
||||||
|
quote = "BTC"
|
||||||
|
rate = 1
|
||||||
|
fee = 0.5
|
||||||
|
swapInFee = 0.1
|
||||||
|
maxSwapAmount = 40_294_967
|
||||||
|
minSwapAmount = 50_000
|
||||||
|
|
||||||
|
[pairs.timeoutDelta]
|
||||||
|
chain = 1440
|
||||||
|
reverse = 1440
|
||||||
|
swapMinimal = 1440
|
||||||
|
swapMaximal = 2880
|
||||||
|
swapTaproot = 10080
|
||||||
|
|
||||||
|
[[pairs]]
|
||||||
|
base = "L-BTC"
|
||||||
|
quote = "BTC"
|
||||||
|
fee = 0.25
|
||||||
|
swapInFee = 0.1
|
||||||
|
rate = 1
|
||||||
|
maxSwapAmount = 40_294_967
|
||||||
|
minSwapAmount = 100
|
||||||
|
|
||||||
|
[pairs.submarineSwap]
|
||||||
|
minSwapAmount = 1_000
|
||||||
|
minBatchedAmount = 21
|
||||||
|
|
||||||
|
[pairs.chainSwap]
|
||||||
|
minSwapAmount = 25_000
|
||||||
|
|
||||||
|
[pairs.timeoutDelta]
|
||||||
|
chain = 1440
|
||||||
|
reverse = 1440
|
||||||
|
swapMinimal = 1440
|
||||||
|
swapMaximal = 2880
|
||||||
|
swapTaproot = 10080
|
||||||
|
|
||||||
|
[[currencies]]
|
||||||
|
symbol = "BTC"
|
||||||
|
network = "bitcoinRegtest"
|
||||||
|
minWalletBalance = 10_000_000
|
||||||
|
minChannelBalance = 10_000_000
|
||||||
|
maxSwapAmount = 40_294_967
|
||||||
|
minSwapAmount = 10_000
|
||||||
|
maxZeroConfAmount = 0
|
||||||
|
|
||||||
|
[currencies.chain]
|
||||||
|
# mempoolSpace = "http://mempool-web:8090/api"
|
||||||
|
host = "bitcoind"
|
||||||
|
zmqpubrawtx = "tcp://bitcoind:29000"
|
||||||
|
zmqpubrawblock = "tcp://bitcoind:29001"
|
||||||
|
port = 18_443
|
||||||
|
cookie = "/root/.bitcoin/regtest/.cookie"
|
||||||
|
feeFloor = 0.2
|
||||||
|
|
||||||
|
wallet = "lnbits"
|
||||||
|
|
||||||
|
[currencies.lnd]
|
||||||
|
host = "lnd-2"
|
||||||
|
port = 10_009
|
||||||
|
certpath = "/data/lnd/tls.cert"
|
||||||
|
macaroonpath = "/data/lnd/data/chain/bitcoin/regtest/admin.macaroon"
|
||||||
|
|
||||||
|
|
||||||
|
[liquid]
|
||||||
|
symbol = "L-BTC"
|
||||||
|
network = "liquidRegtest"
|
||||||
|
|
||||||
|
maxSwapAmount = 40_294_967
|
||||||
|
minSwapAmount = 10_000
|
||||||
|
maxZeroConfAmount = 40_294_967
|
||||||
|
|
||||||
|
|
||||||
|
[liquid.chain]
|
||||||
|
host = "elementsd"
|
||||||
|
port = 18884
|
||||||
|
cookie = "/root/.elements/liquidregtest/.cookie"
|
||||||
|
zmqpubrawtx = "tcp://elementsd:31000"
|
||||||
|
zmqpubhashblock = "tcp://elementsd:31002"
|
||||||
|
|
||||||
|
wallet = "lnbits"
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
eclair {
|
||||||
|
chain = "regtest"
|
||||||
|
|
||||||
|
api {
|
||||||
|
binding-ip = "0.0.0.0"
|
||||||
|
enabled = true
|
||||||
|
port = 8080
|
||||||
|
password = "lnbits"
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcoind {
|
||||||
|
host = "bitcoind"
|
||||||
|
rpcport = 18443
|
||||||
|
auth = "safecookie"
|
||||||
|
cookie = "/root/.bitcoin/regtest/.cookie"
|
||||||
|
|
||||||
|
zmqblock = "tcp://bitcoind:29002"
|
||||||
|
zmqtx = "tcp://bitcoind:29000"
|
||||||
|
}
|
||||||
|
|
||||||
|
channel {
|
||||||
|
max-funding-satoshis = 10000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
option_support_large_channel = mandatory
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,434 @@
|
|||||||
|
services:
|
||||||
|
|
||||||
|
lnbits:
|
||||||
|
hostname: lnbits
|
||||||
|
depends_on:
|
||||||
|
- lnd-3
|
||||||
|
image: lnbits/lnbits
|
||||||
|
restart: on-failure
|
||||||
|
user: "0:0"
|
||||||
|
environment:
|
||||||
|
LNBITS_PORT: 5001
|
||||||
|
DEBUG: true
|
||||||
|
LNBITS_ADMIN_UI: false
|
||||||
|
LNBITS_BACKEND_WALLET_CLASS: "LndRestWallet"
|
||||||
|
LNBITS_DATA_FOLDER: "./data"
|
||||||
|
LND_REST_ENDPOINT: "https://lnd-3:8081/"
|
||||||
|
LND_REST_CERT: "./lnd/tls.cert"
|
||||||
|
LND_REST_MACAROON: "./lnd/data/chain/bitcoin/regtest/admin.macaroon"
|
||||||
|
ports:
|
||||||
|
- 5001:5001
|
||||||
|
volumes:
|
||||||
|
- lnbits-data:/app/data
|
||||||
|
- ./data/lnd-3:/app/lnd:uid=1000,gid=1000
|
||||||
|
|
||||||
|
boltz:
|
||||||
|
hostname: boltz
|
||||||
|
depends_on:
|
||||||
|
- lnd-2
|
||||||
|
- boltz-postgres
|
||||||
|
restart: always
|
||||||
|
image: boltz/boltz:v3.12.1
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
entrypoint: "sh -c 'sleep 30; /boltz-backend/bin/boltzd'"
|
||||||
|
volumes:
|
||||||
|
- ./data/lnd-2:/data/lnd/
|
||||||
|
- ./data/boltz/:/root/.boltz/
|
||||||
|
- elements-data:/root/.elements
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
boltz-client:
|
||||||
|
hostname: boltz-client
|
||||||
|
depends_on:
|
||||||
|
- boltz
|
||||||
|
restart: always
|
||||||
|
image: boltz/boltz-client:latest
|
||||||
|
ports:
|
||||||
|
- 9002:9002
|
||||||
|
- 9003:9003
|
||||||
|
expose:
|
||||||
|
- 9002
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'boltzcli', '--host', 'boltz-client', 'getinfo']
|
||||||
|
interval: 5s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 10
|
||||||
|
start_period: 0s
|
||||||
|
volumes:
|
||||||
|
- elements-data:/root/.elements
|
||||||
|
- ./data/boltz-client:/root/.boltz
|
||||||
|
|
||||||
|
boltz-backend-nginx:
|
||||||
|
hostname: boltz-nginx
|
||||||
|
restart: always
|
||||||
|
image: nginx:latest
|
||||||
|
ports:
|
||||||
|
- 9001:9001
|
||||||
|
volumes:
|
||||||
|
- nginx-data:/etc/nginx/conf.d
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'curl http://localhost:9001/version']
|
||||||
|
timeout: 1s
|
||||||
|
retries: 10
|
||||||
|
interval: 1s
|
||||||
|
start_period: 0s
|
||||||
|
|
||||||
|
boltz-postgres:
|
||||||
|
hostname: boltz-postgres
|
||||||
|
restart: always
|
||||||
|
image: postgres:14-alpine
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready --dbname boltz --username boltz"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 30s
|
||||||
|
retries: 10
|
||||||
|
start_period: 5s
|
||||||
|
environment:
|
||||||
|
- POSTGRES_DB=boltz
|
||||||
|
- POSTGRES_USER=boltz
|
||||||
|
- POSTGRES_PASSWORD=boltz
|
||||||
|
expose:
|
||||||
|
- 5432
|
||||||
|
|
||||||
|
bitcoind:
|
||||||
|
hostname: bitcoind
|
||||||
|
image: boltz/bitcoin-core:25.0
|
||||||
|
command:
|
||||||
|
- -regtest
|
||||||
|
- -fallbackfee=0.00000253
|
||||||
|
- -zmqpubrawtx=tcp://0.0.0.0:29000
|
||||||
|
- -zmqpubrawblock=tcp://0.0.0.0:29001
|
||||||
|
- -zmqpubhashblock=tcp://0.0.0.0:29002
|
||||||
|
- -txindex
|
||||||
|
- -rpcallowip=0.0.0.0/0
|
||||||
|
- -rpcbind=0.0.0.0
|
||||||
|
- -addresstype=bech32
|
||||||
|
- -changetype=bech32
|
||||||
|
- -dbcache=2048
|
||||||
|
- -rpcworkqueue=256
|
||||||
|
volumes:
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
expose:
|
||||||
|
- 29000
|
||||||
|
- 29001
|
||||||
|
- 29002
|
||||||
|
- 18443
|
||||||
|
- 18444
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"bitcoin-cli",
|
||||||
|
"--rpccookiefile=/root/.bitcoin/regtest/.cookie",
|
||||||
|
"-regtest",
|
||||||
|
"getblockchaininfo",
|
||||||
|
]
|
||||||
|
timeout: 1s
|
||||||
|
retries: 1
|
||||||
|
interval: 1s
|
||||||
|
start_period: 0s
|
||||||
|
|
||||||
|
clightning-1:
|
||||||
|
hostname: clightning-1
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/c-lightning:24.11
|
||||||
|
command:
|
||||||
|
- --large-channels
|
||||||
|
- --network=regtest
|
||||||
|
- --grpc-port=9736
|
||||||
|
- --bind-addr=0.0.0.0:9735
|
||||||
|
- --bitcoin-rpcconnect=bitcoind
|
||||||
|
- --bitcoin-rpcport=18443
|
||||||
|
- --clnrest-host=0.0.0.0
|
||||||
|
- --clnrest-port=3010
|
||||||
|
expose:
|
||||||
|
- 9735
|
||||||
|
ports:
|
||||||
|
- 9736:9736
|
||||||
|
- 3010:3010
|
||||||
|
volumes:
|
||||||
|
- ./data/clightning-1:/root/.lightning/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
clightning-2:
|
||||||
|
hostname: clightning-2
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/c-lightning:22.11.1
|
||||||
|
command:
|
||||||
|
- --large-channels
|
||||||
|
- --network=regtest
|
||||||
|
- --grpc-port=9737
|
||||||
|
- --bind-addr=0.0.0.0:9735
|
||||||
|
- --bitcoin-rpcconnect=bitcoind
|
||||||
|
- --bitcoin-rpcport=18443
|
||||||
|
expose:
|
||||||
|
- 9735
|
||||||
|
ports:
|
||||||
|
- 9737:9737
|
||||||
|
volumes:
|
||||||
|
- ./data/clightning-2:/root/.lightning/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
clightning-2-rest:
|
||||||
|
hostname: clightning-2-rest
|
||||||
|
depends_on:
|
||||||
|
- clightning-2
|
||||||
|
image: saubyk/c-lightning-rest:0.10.7
|
||||||
|
entrypoint: "sh -c 'sleep 35 && /sbin/tini -g -- ./docker-entrypoint.sh'"
|
||||||
|
ports:
|
||||||
|
- 3001:3001
|
||||||
|
expose:
|
||||||
|
- 3001
|
||||||
|
volumes:
|
||||||
|
- ./data/clightning-2:/root/.lightning/:uid=1000,gid=1000
|
||||||
|
- ./data/clightning-2-rest:/usr/src/app/certs/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
clightning-3:
|
||||||
|
hostname: clightning-3
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/c-lightning:24.11
|
||||||
|
command:
|
||||||
|
- --large-channels
|
||||||
|
- --network=regtest
|
||||||
|
- --grpc-port=9738
|
||||||
|
- --bind-addr=0.0.0.0:9735
|
||||||
|
- --bitcoin-rpcconnect=bitcoind
|
||||||
|
- --bitcoin-rpcport=18443
|
||||||
|
expose:
|
||||||
|
- 9735
|
||||||
|
ports:
|
||||||
|
- 9738:9738
|
||||||
|
volumes:
|
||||||
|
- ./data/clightning-3:/root/.lightning/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
lnd-1:
|
||||||
|
hostname: lnd-1
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/lnd:0.18.4-beta
|
||||||
|
restart: on-failure
|
||||||
|
command:
|
||||||
|
- --listen=lnd-1:9735
|
||||||
|
- --rpclisten=lnd-1:10009
|
||||||
|
- --restlisten=lnd-1:8081
|
||||||
|
- --bitcoin.active
|
||||||
|
- --bitcoin.regtest
|
||||||
|
- --bitcoin.node=bitcoind
|
||||||
|
- --bitcoind.rpchost=bitcoind
|
||||||
|
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||||
|
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||||
|
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||||
|
- --noseedbackup
|
||||||
|
- --protocol.wumbo-channels
|
||||||
|
expose:
|
||||||
|
- 8081
|
||||||
|
- 9735
|
||||||
|
- 10009
|
||||||
|
volumes:
|
||||||
|
- ./data/lnd-1:/root/.lnd/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
lnd-2:
|
||||||
|
hostname: lnd-2
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/lnd:0.19.3-beta
|
||||||
|
restart: on-failure
|
||||||
|
command:
|
||||||
|
- --listen=lnd-2:9735
|
||||||
|
- --rpclisten=lnd-2:10009
|
||||||
|
- --restlisten=lnd-2:8081
|
||||||
|
- --bitcoin.active
|
||||||
|
- --bitcoin.regtest
|
||||||
|
- --bitcoin.node=bitcoind
|
||||||
|
- --bitcoind.rpchost=bitcoind
|
||||||
|
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||||
|
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||||
|
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||||
|
- --noseedbackup
|
||||||
|
- --protocol.wumbo-channels
|
||||||
|
expose:
|
||||||
|
- 8081
|
||||||
|
- 9735
|
||||||
|
- 10009
|
||||||
|
volumes:
|
||||||
|
- ./data/lnd-2:/root/.lnd/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
lnd-3:
|
||||||
|
hostname: lnd-3
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/lnd:0.18.4-beta
|
||||||
|
restart: on-failure
|
||||||
|
command:
|
||||||
|
- --listen=lnd-3:9735
|
||||||
|
- --rpclisten=lnd-3:10009
|
||||||
|
- --restlisten=lnd-3:8081
|
||||||
|
- --bitcoin.active
|
||||||
|
- --bitcoin.regtest
|
||||||
|
- --bitcoin.node=bitcoind
|
||||||
|
- --bitcoind.rpchost=bitcoind
|
||||||
|
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||||
|
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||||
|
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||||
|
- --noseedbackup
|
||||||
|
- --protocol.wumbo-channels
|
||||||
|
ports:
|
||||||
|
- 8081:8081
|
||||||
|
- 10009:10009
|
||||||
|
expose:
|
||||||
|
- 8081
|
||||||
|
- 9735
|
||||||
|
- 10009
|
||||||
|
volumes:
|
||||||
|
- ./data/lnd-3:/root/.lnd/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
lnd-4:
|
||||||
|
hostname: lnd-4
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/lnd:0.18.4-beta
|
||||||
|
restart: on-failure
|
||||||
|
command:
|
||||||
|
- --listen=lnd-4:9735
|
||||||
|
- --rpclisten=lnd-4:10009
|
||||||
|
- --restlisten=lnd-4:8081
|
||||||
|
- --bitcoin.active
|
||||||
|
- --bitcoin.regtest
|
||||||
|
- --bitcoin.node=bitcoind
|
||||||
|
- --bitcoind.rpchost=bitcoind
|
||||||
|
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||||
|
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||||
|
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||||
|
- --noseedbackup
|
||||||
|
- --protocol.wumbo-channels
|
||||||
|
expose:
|
||||||
|
- 8081
|
||||||
|
- 9735
|
||||||
|
- 10009
|
||||||
|
volumes:
|
||||||
|
- ./data/lnd-4:/root/.lnd/
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
eclair:
|
||||||
|
hostname: eclair
|
||||||
|
depends_on:
|
||||||
|
- bitcoind
|
||||||
|
image: boltz/eclair:0.8.0
|
||||||
|
restart: on-failure
|
||||||
|
entrypoint: "sh -c 'JAVA_OPTS=-Xmx512m /eclair-node/bin/eclair-node.sh -Declair.datadir=/root/eclair -Declair.printToConsole'"
|
||||||
|
ports:
|
||||||
|
- 8082:8080
|
||||||
|
expose:
|
||||||
|
- 9735
|
||||||
|
- 8080
|
||||||
|
volumes:
|
||||||
|
- ./data/eclair:/root/eclair
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
electrs:
|
||||||
|
hostname: electrs
|
||||||
|
restart: always
|
||||||
|
image: boltz/electrs:latest
|
||||||
|
entrypoint: ["electrs-bitcoin"]
|
||||||
|
command:
|
||||||
|
- --electrum-rpc-addr
|
||||||
|
- electrs:19001
|
||||||
|
- --http-addr
|
||||||
|
- electrs:3002
|
||||||
|
- --daemon-rpc-addr
|
||||||
|
- bitcoind:18443
|
||||||
|
- --network
|
||||||
|
- regtest
|
||||||
|
- --jsonrpc-import
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -s $(hostname):3002/blocks/tip/height"]
|
||||||
|
timeout: 1s
|
||||||
|
retries: 20
|
||||||
|
interval: 2s
|
||||||
|
start_period: 5s
|
||||||
|
ports:
|
||||||
|
- 19001:19001
|
||||||
|
- 3002:3002
|
||||||
|
volumes:
|
||||||
|
- bitcoin-data:/root/.bitcoin
|
||||||
|
|
||||||
|
elementsd:
|
||||||
|
hostname: elementsd
|
||||||
|
restart: always
|
||||||
|
image: boltz/elements:latest
|
||||||
|
expose:
|
||||||
|
- 31001
|
||||||
|
ports:
|
||||||
|
- 31000:31000
|
||||||
|
- 31002:31002
|
||||||
|
- 18884:18884
|
||||||
|
command:
|
||||||
|
- -chain=liquidregtest
|
||||||
|
- -txindex=1
|
||||||
|
- -rest=1
|
||||||
|
- -server=1
|
||||||
|
- -rpcallowip=0.0.0.0/0
|
||||||
|
- -validatepegin=0
|
||||||
|
- -initialfreecoins=2100000000000000
|
||||||
|
- -fallbackfee=0.000001
|
||||||
|
- -rpcbind=0.0.0.0
|
||||||
|
- -rpcport=18884
|
||||||
|
- -zmqpubrawtx=tcp://0.0.0.0:31000
|
||||||
|
- -zmqpubrawblock=tcp://0.0.0.0:31001
|
||||||
|
- -zmqpubhashblock=tcp://0.0.0.0:31002
|
||||||
|
- -acceptdiscountct=1
|
||||||
|
- -creatediscountct=1
|
||||||
|
volumes:
|
||||||
|
- elements-data:/root/.elements
|
||||||
|
|
||||||
|
electrs-liquid:
|
||||||
|
hostname: electrs-liquid
|
||||||
|
restart: always
|
||||||
|
image: boltz/electrs:latest
|
||||||
|
entrypoint: ["electrs-liquid"]
|
||||||
|
command:
|
||||||
|
- --electrum-rpc-addr
|
||||||
|
- electrs-liquid:19002
|
||||||
|
- --http-addr
|
||||||
|
- electrs-liquid:3003
|
||||||
|
- --daemon-rpc-addr
|
||||||
|
- elementsd:18884
|
||||||
|
- --daemon-dir
|
||||||
|
- /root/.elements
|
||||||
|
- --network
|
||||||
|
- liquidregtest
|
||||||
|
- --parent-network
|
||||||
|
- regtest
|
||||||
|
- --jsonrpc-import
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -s $(hostname):3003/blocks/tip/height"]
|
||||||
|
timeout: 15s
|
||||||
|
retries: 20
|
||||||
|
interval: 2s
|
||||||
|
start_period: 5s
|
||||||
|
ports:
|
||||||
|
- 19002:19002
|
||||||
|
- 3003:3003
|
||||||
|
volumes:
|
||||||
|
- elements-data:/root/.elements
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
lnbits-data:
|
||||||
|
bitcoin-data:
|
||||||
|
elements-data:
|
||||||
|
nginx-data:
|
||||||
|
name: nginx-data
|
||||||
|
driver: local
|
||||||
|
driver_opts:
|
||||||
|
type: none
|
||||||
|
o: bind
|
||||||
|
device: ./data/boltz-nginx/
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
export COMPOSE_PROJECT_NAME=lnbits
|
||||||
|
|
||||||
|
boltzcli-sim() {
|
||||||
|
docker exec -it lnbits-boltz-client-1 boltzcli "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
bitcoin-cli-sim() {
|
||||||
|
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
elements-cli-sim() {
|
||||||
|
docker exec lnbits-elementsd-1 elements-cli -rpcport=18884 -chain=liquidregtest "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# args(i, cmd)
|
||||||
|
lightning-cli-sim() {
|
||||||
|
i=$1
|
||||||
|
shift # shift first argument so we can use $@
|
||||||
|
docker exec lnbits-clightning-$i-1 lightning-cli --network regtest "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
# args(i, cmd)
|
||||||
|
lncli-sim() {
|
||||||
|
i=$1
|
||||||
|
shift # shift first argument so we can use $@
|
||||||
|
docker exec lnbits-lnd-$i-1 lncli --network regtest --rpcserver=lnd-$i:10009 "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
get-eclair-pubkey() {
|
||||||
|
while true; do
|
||||||
|
pubkey=$(docker exec lnbits-eclair-1 curl http://localhost:8080/getinfo -X POST -s -u :lnbits | jq -r .nodeId 2> /dev/null)
|
||||||
|
pubkeyPrefix=$(echo $pubkey | cut -c1,2)
|
||||||
|
if [[ "$pubkeyPrefix" == "02" || "$pubkeyPrefix" == "03" ]]; then
|
||||||
|
echo $pubkey
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait-for-eclair-channel() {
|
||||||
|
while true; do
|
||||||
|
state=$(docker exec lnbits-eclair-1 curl http://localhost:8080/channels -X POST -s -u :lnbits | jq -r ".[0].state")
|
||||||
|
pending=$(docker exec lnbits-eclair-1 curl -s http://localhost:8080/channels -X POST -u :lnbits| jq '. | length')
|
||||||
|
echo "eclair-1 pendingchannels: $pending, current state: $state"
|
||||||
|
if [[ "$state" == "NORMAL" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# args(i)
|
||||||
|
fund_boltz_client() {
|
||||||
|
# first address of seed: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
|
||||||
|
address="el1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f3mmz5l7uw5pqmx6xf5xy50hsn6vhkm5euwt72x878eq6zxx2z0z676mna6kdq"
|
||||||
|
echo "funding: $address on boltz-client"
|
||||||
|
elements-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# args(i)
|
||||||
|
fund_clightning_node() {
|
||||||
|
address=$(lightning-cli-sim $1 newaddr | jq -r .bech32)
|
||||||
|
echo "funding: $address on clightning-node: $1"
|
||||||
|
bitcoin-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# args(i)
|
||||||
|
fund_lnd_node() {
|
||||||
|
address=$(lncli-sim $1 newaddress p2wkh | jq -r .address)
|
||||||
|
echo "funding: $address on lnd-node: $1"
|
||||||
|
bitcoin-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
# args(i, j)
|
||||||
|
connect_clightning_node() {
|
||||||
|
pubkey=$(lightning-cli-sim $2 getinfo | jq -r '.id')
|
||||||
|
lightning-cli-sim $1 connect $pubkey@lnbits-clightning-$2-1:9735 | jq -r '.id'
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-regtest-start(){
|
||||||
|
if ! command -v jq &> /dev/null
|
||||||
|
then
|
||||||
|
echo "jq is not installed"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
if ! command -v docker &> /dev/null
|
||||||
|
then
|
||||||
|
echo "docker is not installed"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
if ! command -v docker version &> /dev/null
|
||||||
|
then
|
||||||
|
echo "dockerd is not running"
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
lnbits-regtest-stop
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
|
lnbits-regtest-init
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-regtest-start-log(){
|
||||||
|
lnbits-regtest-stop
|
||||||
|
docker compose up --remove-orphans
|
||||||
|
lnbits-regtest-init
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-regtest-stop(){
|
||||||
|
docker compose down --volumes
|
||||||
|
# clean up lightning node data
|
||||||
|
sudo rm -rf ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4 ./data/boltz/boltz.db ./data/eclair/regtest ./data/boltz-client/liquid-wallet ./data/boltz-client/bitcoin-wallet ./data/boltz-client/wallet ./data/boltz-client/boltz.db
|
||||||
|
# recreate lightning node data folders preventing permission errors
|
||||||
|
mkdir ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-regtest-restart(){
|
||||||
|
lnbits-regtest-stop
|
||||||
|
lnbits-regtest-start
|
||||||
|
}
|
||||||
|
|
||||||
|
boltz-client-init(){
|
||||||
|
for i in 0 1 2; do
|
||||||
|
fund_boltz_client
|
||||||
|
done
|
||||||
|
elements-cli-sim -generate 3 > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-bitcoin-init(){
|
||||||
|
echo "init_bitcoin_wallet..."
|
||||||
|
bitcoin-cli-sim createwallet lnbits || bitcoin-cli-sim loadwallet lnbits
|
||||||
|
echo "mining 150 blocks..."
|
||||||
|
bitcoin-cli-sim -generate 150 > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-elements-init(){
|
||||||
|
echo "init_elements_wallet..."
|
||||||
|
elements-cli-sim createwallet lnbits || elements-cli-sim loadwallet lnbits
|
||||||
|
echo "mining 150 blocks..."
|
||||||
|
elements-cli-sim -generate 150 > /dev/null
|
||||||
|
elements-cli-sim rescanblockchain
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-init(){
|
||||||
|
echo "init_lnbits..."
|
||||||
|
docker exec lnbits-lnbits-1 uv run python tools/create_fake_admin.py
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-regtest-init(){
|
||||||
|
lnbits-bitcoin-init
|
||||||
|
lnbits-elements-init
|
||||||
|
lnbits-lightning-sync
|
||||||
|
lnbits-lightning-init
|
||||||
|
boltz-client-init
|
||||||
|
lnbits-init
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-lightning-sync(){
|
||||||
|
wait-for-clightning-sync 1
|
||||||
|
wait-for-clightning-sync 2
|
||||||
|
wait-for-clightning-sync 3
|
||||||
|
wait-for-lnd-sync 1
|
||||||
|
wait-for-lnd-sync 2
|
||||||
|
wait-for-lnd-sync 3
|
||||||
|
wait-for-lnd-sync 4
|
||||||
|
}
|
||||||
|
|
||||||
|
lnbits-lightning-init(){
|
||||||
|
|
||||||
|
# create 10 UTXOs for each node
|
||||||
|
for i in 0 1 2; do
|
||||||
|
fund_clightning_node 1
|
||||||
|
fund_clightning_node 2
|
||||||
|
fund_clightning_node 3
|
||||||
|
fund_lnd_node 1
|
||||||
|
fund_lnd_node 2
|
||||||
|
fund_lnd_node 3
|
||||||
|
fund_lnd_node 4
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "mining 3 blocks..."
|
||||||
|
bitcoin-cli-sim -generate 3 > /dev/null
|
||||||
|
|
||||||
|
lnbits-lightning-sync
|
||||||
|
|
||||||
|
channel_confirms=6
|
||||||
|
channel_size=24000000 # 0.024 btc
|
||||||
|
balance_size=12000000 # 0.12 btc
|
||||||
|
balance_size_msat=12000000000 # 0.12 btc
|
||||||
|
|
||||||
|
# lnd-1 -> lnd-2
|
||||||
|
lncli-sim 1 connect $(lncli-sim 2 getinfo | jq -r '.identity_pubkey')@lnbits-lnd-2-1 > /dev/null
|
||||||
|
echo "open channel from lnd-1 to lnd-2"
|
||||||
|
lncli-sim 1 openchannel $(lncli-sim 2 getinfo | jq -r '.identity_pubkey') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-1 -> lnd-3
|
||||||
|
lncli-sim 1 connect $(lncli-sim 3 getinfo | jq -r '.identity_pubkey')@lnbits-lnd-3-1 > /dev/null
|
||||||
|
echo "open channel from lnd-1 to lnd-3"
|
||||||
|
lncli-sim 1 openchannel $(lncli-sim 3 getinfo | jq -r '.identity_pubkey') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-1 -> cln-1
|
||||||
|
lncli-sim 1 connect $(lightning-cli-sim 1 getinfo | jq -r '.id')@lnbits-clightning-1-1 > /dev/null
|
||||||
|
echo "open channel from lnd-1 to cln-1"
|
||||||
|
lncli-sim 1 openchannel $(lightning-cli-sim 1 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-1 -> cln-2
|
||||||
|
lncli-sim 1 connect $(lightning-cli-sim 2 getinfo | jq -r '.id')@lnbits-clightning-2-1 > /dev/null
|
||||||
|
echo "open channel from lnd-1 to cln-2"
|
||||||
|
lncli-sim 1 openchannel $(lightning-cli-sim 2 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-1 -> cln-3
|
||||||
|
lncli-sim 1 connect $(lightning-cli-sim 3 getinfo | jq -r '.id')@lnbits-clightning-3-1 > /dev/null
|
||||||
|
echo "open channel from lnd-1 to cln-3"
|
||||||
|
lncli-sim 1 openchannel $(lightning-cli-sim 3 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-2 -> cln-2
|
||||||
|
lncli-sim 2 connect $(lightning-cli-sim 2 getinfo | jq -r '.id')@lnbits-clightning-2-1 > /dev/null
|
||||||
|
echo "open channel from lnd-2 to cln-2"
|
||||||
|
lncli-sim 2 openchannel $(lightning-cli-sim 2 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 2
|
||||||
|
|
||||||
|
# lnd-3 -> cln-3
|
||||||
|
lncli-sim 3 connect $(lightning-cli-sim 3 getinfo | jq -r '.id')@lnbits-clightning-3-1 > /dev/null
|
||||||
|
echo "open channel from lnd-3 to cln-1"
|
||||||
|
lncli-sim 3 openchannel $(lightning-cli-sim 3 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 3
|
||||||
|
|
||||||
|
# lnd-3 -> cln-1
|
||||||
|
lncli-sim 3 connect $(lightning-cli-sim 1 getinfo | jq -r '.id')@lnbits-clightning-1-1 > /dev/null
|
||||||
|
echo "open channel from lnd-3 to cln-1"
|
||||||
|
lncli-sim 3 openchannel $(lightning-cli-sim 1 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 3
|
||||||
|
|
||||||
|
# lnd-1 -> eclair-1
|
||||||
|
lncli-sim 1 connect $(get-eclair-pubkey)@lnbits-eclair-1 > /dev/null
|
||||||
|
echo "open channel from lnd-2 to eclair-1"
|
||||||
|
lncli-sim 1 openchannel $(get-eclair-pubkey) $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 1
|
||||||
|
|
||||||
|
# lnd-2 -> eclair-1
|
||||||
|
lncli-sim 2 connect $(get-eclair-pubkey)@lnbits-eclair-1 > /dev/null
|
||||||
|
echo "open channel from lnd-2 to eclair-1"
|
||||||
|
lncli-sim 2 openchannel $(get-eclair-pubkey) $channel_size $balance_size > /dev/null
|
||||||
|
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||||
|
wait-for-lnd-channel 2
|
||||||
|
|
||||||
|
wait-for-clightning-channel 1
|
||||||
|
wait-for-clightning-channel 2
|
||||||
|
wait-for-clightning-channel 3
|
||||||
|
|
||||||
|
wait-for-eclair-channel
|
||||||
|
|
||||||
|
lnbits-lightning-sync
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
wait-for-lnd-channel(){
|
||||||
|
while true; do
|
||||||
|
pending=$(lncli-sim $1 pendingchannels | jq -r '.pending_open_channels | length')
|
||||||
|
echo "lnd-$1 pendingchannels: $pending"
|
||||||
|
if [[ "$pending" == "0" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait-for-lnd-sync(){
|
||||||
|
while true; do
|
||||||
|
if [[ "$(lncli-sim $1 getinfo 2>&1 | jq -r '.synced_to_chain' 2> /dev/null)" == "true" ]]; then
|
||||||
|
echo "lnd-$1 is synced!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "waiting for lnd-$1 to sync..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait-for-clightning-channel(){
|
||||||
|
while true; do
|
||||||
|
pending=$(lightning-cli-sim $1 getinfo | jq -r '.num_pending_channels | length')
|
||||||
|
echo "cln-$1 pendingchannels: $pending"
|
||||||
|
if [[ "$pending" == "0" ]]; then
|
||||||
|
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_bitcoind_sync' 2> /dev/null)" == "null" ]]; then
|
||||||
|
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_lightningd_sync' 2> /dev/null)" == "null" ]]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
wait-for-clightning-sync(){
|
||||||
|
while true; do
|
||||||
|
if [[ ! "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.id' 2> /dev/null)" == "null" ]]; then
|
||||||
|
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_bitcoind_sync' 2> /dev/null)" == "null" ]]; then
|
||||||
|
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_lightningd_sync' 2> /dev/null)" == "null" ]]; then
|
||||||
|
echo "cln-$1 is synced!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "waiting for cln-$1 to sync..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
}
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
print_success() {
|
||||||
|
printf "\033[;1;32mPASSED\033[;0m $1\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
printf "\033[;1;31mFAILED\033[;0m $1\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
run(){
|
||||||
|
label=$1
|
||||||
|
value=$2
|
||||||
|
cmd=$3
|
||||||
|
if [[ "$cmd" == "$value" ]]; then
|
||||||
|
print_success "$label is $cmd"
|
||||||
|
else
|
||||||
|
print_error "$label is $cmd, should be $value"
|
||||||
|
failed="true"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
failed="false"
|
||||||
|
blockheight=213
|
||||||
|
utxos=3
|
||||||
|
channel_size=24000000 # 0.024 btc
|
||||||
|
balance_size=12000000 # 0.012 btc
|
||||||
|
|
||||||
|
source $(pwd)/docker-scripts.sh
|
||||||
|
lnbits-regtest-start
|
||||||
|
echo "=================================="
|
||||||
|
printf "\033[;1;36mregtest started! starting tests...\033[;0m\n"
|
||||||
|
echo "=================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
for i in 1 2 3; do
|
||||||
|
run "lnd-$i .synced_to_chain" "true" $(lncli-sim $i getinfo | jq -r ".synced_to_chain")
|
||||||
|
run "lnd-$i utxo count" $utxos $(lncli-sim $i listunspent | jq -r ".utxos | length")
|
||||||
|
run "lnd-$i .block_height" $blockheight $(lncli-sim $i getinfo | jq -r ".block_height")
|
||||||
|
if [[ "$i" == "1" ]]; then
|
||||||
|
channel_count=6
|
||||||
|
else
|
||||||
|
channel_count=3
|
||||||
|
fi
|
||||||
|
run "lnd-$i openchannels" $channel_count $(lncli-sim $i listchannels | jq -r ".channels | length")
|
||||||
|
run "lnd-$i .channels[0].capacity" $channel_size $(lncli-sim $i listchannels | jq -r ".channels[0].capacity")
|
||||||
|
run "lnd-$i .channels[0].push_amount_sat" $balance_size $(lncli-sim $i listchannels | jq -r ".channels[0].push_amount_sat")
|
||||||
|
done
|
||||||
|
for i in 1 2 3; do
|
||||||
|
# run "cln-$i blockheight" $blockheight $(lightning-cli-sim $i getinfo | jq -r ".blockheight")
|
||||||
|
run "cln-$i utxo count" $utxos $(lightning-cli-sim $i listfunds | jq -r ".outputs | length")
|
||||||
|
run "cln-$i openchannels" 2 $(lightning-cli-sim $i getinfo | jq -r ".num_active_channels")
|
||||||
|
run "cln-$i channel[0].state" "CHANNELD_NORMAL" $(lightning-cli-sim $i listfunds | jq -r ".channels[0].state")
|
||||||
|
run "cln-$i channel[0].amount_msat" $(($channel_size * 1000)) $(lightning-cli-sim $i listfunds | jq -r ".channels[0].amount_msat" | sed 's/msat//g')
|
||||||
|
run "cln-$i channel[0].our_amount_msat" $(($balance_size * 1000)) $(lightning-cli-sim $i listfunds | jq -r ".channels[0].our_amount_msat" | sed 's/msat//g')
|
||||||
|
done
|
||||||
|
|
||||||
|
run "eclair-1 openchannels" 2 $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/channels -X POST -u :lnbits| jq '. | length')
|
||||||
|
run "eclair-1 blockHeight" $blockheight $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/getinfo -X POST -u :lnbits| jq '.blockHeight')
|
||||||
|
run "lnbits service status" "200" $(curl -s -o /dev/null -w "%{http_code}" "http://localhost:5001/")
|
||||||
|
run "boltz service status" "200" $(curl -s -o /dev/null --head -w "%{http_code}" "http://localhost:9001/version")
|
||||||
|
|
||||||
|
# return non-zero exit code if a test fails
|
||||||
|
if [[ "$failed" == "true" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo "=================================="
|
||||||
|
print_error "one more more tests failed"
|
||||||
|
echo "=================================="
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "=================================="
|
||||||
|
print_success "all tests passed! yay!"
|
||||||
|
echo "=================================="
|
||||||
|
fi
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
ARG LNBITS_TAG=latest
|
||||||
|
|
||||||
|
FROM lnbits/lnbits:${LNBITS_TAG}
|
||||||
|
|
||||||
|
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get -y upgrade
|
||||||
|
RUN apt-get install -y ca-certificates curl gnupg netcat-openbsd git nodejs
|
||||||
|
|
||||||
|
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
|
# install sparksidecar
|
||||||
|
RUN git clone https://github.com/lnbits/spark_sidecar
|
||||||
|
RUN cd spark_sidecar && npm ci
|
||||||
|
|
||||||
|
# Reinstall dependencies for lnbits just in case (needed for CMD usage)
|
||||||
|
RUN uv sync --all-extras
|
||||||
|
|
||||||
|
# LNBITS
|
||||||
|
ENV LNBITS_PORT="5000"
|
||||||
|
ENV LNBITS_HOST="0.0.0.0"
|
||||||
|
ENV LNBITS_BACKEND_WALLET_CLASS="SparkL2Wallet"
|
||||||
|
ENV LNBITS_RESERVE_FEE_MIN="20000"
|
||||||
|
ENV LNBITS_RESERVE_FEE_PERCENT="1"
|
||||||
|
ENV LNBITS_FUNDING_SOURCE_PAY_INVOICE_WAIT_SECONDS="20"
|
||||||
|
ENV FUNDING_SOURCE_MAX_RETRIES="10"
|
||||||
|
|
||||||
|
# spark sidecar
|
||||||
|
ENV SPARK_NETWORK="MAINNET"
|
||||||
|
ENV SPARK_SIDECAR_PORT="8765"
|
||||||
|
ENV SPARK_PAY_WAIT_MS="20000"
|
||||||
|
ENV SPARK_MULTIPLICITY="3"
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
COPY entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
# Entrypoint to start sparksidebar and LNbits
|
||||||
|
CMD ["/entrypoint.sh"]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
node spark_sidecar/server.mjs &
|
||||||
|
|
||||||
|
SIDECAR_PID=$!
|
||||||
|
|
||||||
|
# Wait for startup
|
||||||
|
for i in {1..10}; do
|
||||||
|
if nc -z localhost $SPARK_SIDECAR_PORT; then
|
||||||
|
echo "sparksidebar is up!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
echo "Waiting for sparksidebar to start..."
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Optional: check if still not up
|
||||||
|
if ! nc -z localhost $SPARK_SIDECAR_PORT; then
|
||||||
|
echo "sparksidebar did not start successfully."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting LNbits on $LNBITS_HOST:$LNBITS_PORT..."
|
||||||
|
exec uv run lnbits --port "$LNBITS_PORT" --host "$LNBITS_HOST" --forwarded-allow-ips='*'
|
||||||
@@ -50,6 +50,7 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
|
|||||||
| **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
|
| **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
|
||||||
| **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
|
| **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
|
||||||
| **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
|
| **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
|
||||||
|
| **Spark (L2)** | Self-custodial | ❌ | Medium | ❌ | High | Automatic | Easy | Low | Minimal fees | Medium | Runs via Spark sidecar; seed-based self-custody. |
|
||||||
| **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
|
| **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
|
||||||
| **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
| **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||||
| **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
| **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||||
@@ -57,7 +58,9 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
|
|||||||
| **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. |
|
| **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. |
|
||||||
| **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). |
|
| **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). |
|
||||||
|
|
||||||
## TODO: add Spark
|
## Spark (L2)
|
||||||
|
|
||||||
|
Spark L2 uses a local Node.js sidecar to expose an HTTP API that LNbits can use as a funding source. It is self-custodial and secured by a standard mnemonic seed. Sidecar repo `https://github.com/lnbits/spark_sidecar`.
|
||||||
|
|
||||||
### Notes for readers
|
### Notes for readers
|
||||||
|
|
||||||
|
|||||||
+29
-3
@@ -41,7 +41,8 @@ A backend wallet is selected and configured entirely through LNbits environment
|
|||||||
| [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) |
|
| [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) |
|
||||||
| [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) |
|
| [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) |
|
||||||
| [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) |
|
| [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) |
|
||||||
| [Cliche Wallet](#cliche-wallet) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
|
| [Spark L2](#spark-l2) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
|
||||||
|
| [Cliche Wallet](#cliche-wallet) | | |
|
||||||
| [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) |
|
| [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) |
|
||||||
| [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) |
|
| [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) |
|
||||||
| [Fake Wallet](#fake-wallet) | | |
|
| [Fake Wallet](#fake-wallet) | | |
|
||||||
@@ -127,6 +128,33 @@ Old REST interface using [RTL c-lightning-REST](https://github.com/Ride-The-Ligh
|
|||||||
- `SPARK_URL`: `http://10.147.17.230:9737/rpc`
|
- `SPARK_URL`: `http://10.147.17.230:9737/rpc`
|
||||||
- `SPARK_TOKEN`: `secret_access_key`
|
- `SPARK_TOKEN`: `secret_access_key`
|
||||||
|
|
||||||
|
## Spark L2
|
||||||
|
|
||||||
|
Self-custodial funding source using the [Spark L2](https://docs.spark.money/start/overview) network. Requires a Node.js [sidecar](https://github.com/lnbits/spark_sidecar) that bridges lnbits talking to Spark. Works in addition with any Spark-compatible seed (Wallet of Satoshi, BuhoGO, BlitzWallet).
|
||||||
|
If the sidecar is started with a `mnemonic` then that mnemonic will be used. Otherwhise if a mnemonic is set for the `Spark L2` LNbits funding source then that mnemonic will be used.
|
||||||
|
|
||||||
|
### Optional tuning
|
||||||
|
|
||||||
|
- `SPARK_L2_PAY_WAIT_MS`: `4000` _(payment timeout in ms)_
|
||||||
|
- `SPARK_L2_PAY_POLL_MS`: `500` _(polling interval in ms)_
|
||||||
|
- `SPARK_L2_STREAM_KEEPALIVE_MS`: `15000` _(SSE keepalive in ms)_
|
||||||
|
|
||||||
|
### Example: run the sidecar
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/lnbits/spark_sidecar.git
|
||||||
|
cd spark_sidecar
|
||||||
|
npm install
|
||||||
|
|
||||||
|
SPARK_MNEMONIC="bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom" \
|
||||||
|
SPARK_NETWORK=MAINNET \
|
||||||
|
SPARK_SIDECAR_PORT=8765 \
|
||||||
|
SPARK_PAY_WAIT_MS=20000 \
|
||||||
|
node server.mjs
|
||||||
|
```
|
||||||
|
|
||||||
|
For testing, you can generate a 12-word mnemonic at https://iancoleman.io/bip39/. Store it securely — it controls your funds. Then select Spark (L2) as the funding source in LNbits.
|
||||||
|
|
||||||
## LND (REST)
|
## LND (REST)
|
||||||
|
|
||||||
**Required env vars**
|
**Required env vars**
|
||||||
@@ -338,8 +366,6 @@ Configure in the admin UI or via env vars:
|
|||||||
|
|
||||||
<a id="strike"></a>
|
<a id="strike"></a>
|
||||||
|
|
||||||
## TODO: add Spark
|
|
||||||
|
|
||||||
## Strike (alpha)
|
## Strike (alpha)
|
||||||
|
|
||||||
Custodial provider integrated via **Strike OAuth Connect** (OAuth 2.0 / OIDC). Authenticate a Strike user in your app, then call Strike APIs on the user’s behalf once scopes are granted. Requires a Strike business account, registered OAuth client, minimal scopes, and login/logout redirect URLs.
|
Custodial provider integrated via **Strike OAuth Connect** (OAuth 2.0 / OIDC). Authenticate a Strike user in your app, then call Strike APIs on the user’s behalf once scopes are granted. Requires a Strike business account, registered OAuth client, minimal scopes, and login/logout redirect URLs.
|
||||||
|
|||||||
+76
-21
@@ -4,10 +4,12 @@ from typing import Any
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
from lnbits.core.crud.extensions import get_user_active_extensions_ids
|
||||||
from lnbits.core.crud.wallets import create_wallet, get_wallets
|
from lnbits.core.crud.wallets import clear_wallet_cache, create_wallet, get_wallets
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.core.models import UserAcls
|
from lnbits.core.models import UserAcls
|
||||||
from lnbits.db import Connection, Filters, Page
|
from lnbits.db import Connection, Filters, Page
|
||||||
|
from lnbits.helpers import sha256s
|
||||||
|
from lnbits.utils.cache import cache
|
||||||
|
|
||||||
from ..models import (
|
from ..models import (
|
||||||
Account,
|
Account,
|
||||||
@@ -41,6 +43,7 @@ async def delete_account(user_id: str, conn: Connection | None = None) -> None:
|
|||||||
"DELETE from accounts WHERE id = :user",
|
"DELETE from accounts WHERE id = :user",
|
||||||
{"user": user_id},
|
{"user": user_id},
|
||||||
)
|
)
|
||||||
|
await clear_user_id_cache(user_id)
|
||||||
|
|
||||||
|
|
||||||
async def get_accounts(
|
async def get_accounts(
|
||||||
@@ -69,6 +72,7 @@ async def get_accounts(
|
|||||||
accounts.email,
|
accounts.email,
|
||||||
accounts.pubkey,
|
accounts.pubkey,
|
||||||
accounts.external_id,
|
accounts.external_id,
|
||||||
|
accounts.activated,
|
||||||
SUM(COALESCE((
|
SUM(COALESCE((
|
||||||
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
||||||
), 0)) as balance_msat,
|
), 0)) as balance_msat,
|
||||||
@@ -93,12 +97,18 @@ async def get_accounts(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account(user_id: str, conn: Connection | None = None) -> Account | None:
|
async def get_account(
|
||||||
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
||||||
|
) -> Account | None:
|
||||||
if len(user_id) == 0:
|
if len(user_id) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM accounts WHERE id = :id",
|
"""
|
||||||
{"id": user_id},
|
SELECT * FROM accounts
|
||||||
|
WHERE id = :id AND (activated = true OR activated = :activated)
|
||||||
|
""",
|
||||||
|
{"id": user_id, "activated": active_only},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -124,55 +134,79 @@ async def delete_accounts_no_wallets(
|
|||||||
|
|
||||||
|
|
||||||
async def get_account_by_username(
|
async def get_account_by_username(
|
||||||
username: str, conn: Connection | None = None
|
username: str, active_only: bool = True, conn: Connection | None = None
|
||||||
) -> Account | None:
|
) -> Account | None:
|
||||||
if len(username) == 0:
|
if len(username) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM accounts WHERE LOWER(username) = :username",
|
"""
|
||||||
{"username": username.lower()},
|
SELECT * FROM accounts
|
||||||
|
WHERE
|
||||||
|
LOWER(username) = :username
|
||||||
|
AND (activated = true OR activated = :activated)
|
||||||
|
""",
|
||||||
|
{"username": username.lower(), "activated": active_only},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_pubkey(
|
async def get_account_by_pubkey(
|
||||||
pubkey: str, conn: Connection | None = None
|
pubkey: str, active_only: bool = True, conn: Connection | None = None
|
||||||
) -> Account | None:
|
) -> Account | None:
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM accounts WHERE LOWER(pubkey) = :pubkey",
|
"""
|
||||||
{"pubkey": pubkey.lower()},
|
SELECT * FROM accounts
|
||||||
|
WHERE
|
||||||
|
LOWER(pubkey) = :pubkey
|
||||||
|
AND (activated = true OR activated = :activated)
|
||||||
|
""",
|
||||||
|
{"pubkey": pubkey.lower(), "activated": active_only},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_email(
|
async def get_account_by_email(
|
||||||
email: str, conn: Connection | None = None
|
email: str, active_only: bool = True, conn: Connection | None = None
|
||||||
) -> Account | None:
|
) -> Account | None:
|
||||||
if len(email) == 0:
|
if len(email) == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"SELECT * FROM accounts WHERE LOWER(email) = :email",
|
"""
|
||||||
{"email": email.lower()},
|
SELECT * FROM accounts
|
||||||
|
WHERE
|
||||||
|
LOWER(email) = :email
|
||||||
|
AND (activated = true OR activated = :activated)
|
||||||
|
""",
|
||||||
|
{"email": email.lower(), "activated": active_only},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account_by_username_or_email(
|
async def get_account_by_username_or_email(
|
||||||
username_or_email: str, conn: Connection | None = None
|
username_or_email: str,
|
||||||
|
active_only: bool = True,
|
||||||
|
conn: Connection | None = None,
|
||||||
) -> Account | None:
|
) -> Account | None:
|
||||||
|
|
||||||
return await (conn or db).fetchone(
|
return await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM accounts
|
SELECT * FROM accounts
|
||||||
WHERE LOWER(email) = :value or LOWER(username) = :value
|
WHERE
|
||||||
|
(LOWER(email) = :value or LOWER(username) = :value)
|
||||||
|
AND (activated = true OR activated = :activated)
|
||||||
""",
|
""",
|
||||||
{"value": username_or_email.lower()},
|
{"value": username_or_email.lower(), "activated": active_only},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_user(user_id: str, conn: Connection | None = None) -> User | None:
|
async def get_user(
|
||||||
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
||||||
|
) -> User | None:
|
||||||
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:
|
||||||
account = await get_account(user_id, conn=conn)
|
account = await get_account(user_id, active_only, conn=conn)
|
||||||
if not account:
|
if not account:
|
||||||
return None
|
return None
|
||||||
return await get_user_from_account(account, conn=conn)
|
return await get_user_from_account(account, conn=conn)
|
||||||
@@ -191,6 +225,7 @@ async def get_user_from_account(
|
|||||||
|
|
||||||
return User(
|
return User(
|
||||||
id=account.id,
|
id=account.id,
|
||||||
|
activated=account.activated,
|
||||||
email=account.email,
|
email=account.email,
|
||||||
username=account.username,
|
username=account.username,
|
||||||
pubkey=account.pubkey,
|
pubkey=account.pubkey,
|
||||||
@@ -204,6 +239,7 @@ async def get_user_from_account(
|
|||||||
super_user=account.is_super_user,
|
super_user=account.is_super_user,
|
||||||
fiat_providers=account.fiat_providers,
|
fiat_providers=account.fiat_providers,
|
||||||
has_password=account.password_hash is not None,
|
has_password=account.password_hash is not None,
|
||||||
|
ui_customization=account.ui_customization or {},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -215,12 +251,31 @@ async def update_user_access_control_list(
|
|||||||
|
|
||||||
|
|
||||||
async def get_user_access_control_lists(
|
async def get_user_access_control_lists(
|
||||||
user_id: str, conn: Connection | None = None
|
user_id: str, active_only: bool = True, conn: Connection | None = None
|
||||||
) -> UserAcls:
|
) -> UserAcls:
|
||||||
user_acls = await (conn or db).fetchone(
|
user_acls = await (conn or db).fetchone(
|
||||||
"SELECT id, access_control_list FROM accounts WHERE id = :id",
|
"""
|
||||||
{"id": user_id},
|
SELECT id, access_control_list FROM accounts
|
||||||
|
WHERE id = :user_id AND (activated = true OR activated = :activated)
|
||||||
|
""",
|
||||||
|
{"user_id": user_id, "activated": active_only},
|
||||||
UserAcls,
|
UserAcls,
|
||||||
)
|
)
|
||||||
|
|
||||||
return user_acls or UserAcls(id=user_id)
|
return user_acls or UserAcls(id=user_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def clear_user_id_cache(user_id: str):
|
||||||
|
user = await get_user(user_id, active_only=True)
|
||||||
|
if user:
|
||||||
|
clear_user_cache(user)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_user_cache(user: User):
|
||||||
|
user_cache_key: str | None = cache.pop(
|
||||||
|
f"auth:user:cache_key:{sha256s(user.id)}", None
|
||||||
|
)
|
||||||
|
if user_cache_key:
|
||||||
|
cache.pop(user_cache_key)
|
||||||
|
for wallet in user.wallets:
|
||||||
|
clear_wallet_cache(wallet)
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async def delete_wallet(
|
|||||||
deleted: bool = True,
|
deleted: bool = True,
|
||||||
conn: Connection | None = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
_clear_wallet_cache(wallet_id)
|
clear_wallet_id_cache(wallet_id)
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
@@ -65,7 +65,7 @@ async def delete_wallet(
|
|||||||
|
|
||||||
|
|
||||||
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) -> None:
|
||||||
_clear_wallet_cache(wallet_id)
|
clear_wallet_id_cache(wallet_id)
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"DELETE FROM wallets WHERE id = :wallet",
|
"DELETE FROM wallets WHERE id = :wallet",
|
||||||
{"wallet": wallet_id},
|
{"wallet": wallet_id},
|
||||||
@@ -75,7 +75,7 @@ async def force_delete_wallet(wallet_id: str, conn: Connection | None = None) ->
|
|||||||
async def delete_wallet_by_id(
|
async def delete_wallet_by_id(
|
||||||
wallet_id: str, conn: Connection | None = None
|
wallet_id: str, conn: Connection | None = None
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
_clear_wallet_cache(wallet_id)
|
clear_wallet_id_cache(wallet_id)
|
||||||
now = int(time())
|
now = int(time())
|
||||||
result = await (conn or db).execute(
|
result = await (conn or db).execute(
|
||||||
# Timestamp placeholder is safe from SQL injection (not user input)
|
# Timestamp placeholder is safe from SQL injection (not user input)
|
||||||
@@ -226,11 +226,14 @@ async def get_wallet_for_key(
|
|||||||
) -> Wallet | None:
|
) -> Wallet | None:
|
||||||
wallet = await (conn or db).fetchone(
|
wallet = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT *, COALESCE((
|
SELECT wallets.*, COALESCE((
|
||||||
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
SELECT balance FROM balances WHERE wallet_id = wallets.id
|
||||||
), 0)
|
), 0)
|
||||||
AS balance_msat FROM wallets
|
AS balance_msat FROM wallets
|
||||||
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
|
INNER JOIN accounts ON wallets.user = accounts.id
|
||||||
|
WHERE (adminkey = :key OR inkey = :key)
|
||||||
|
AND deleted = false
|
||||||
|
AND accounts.activated = true
|
||||||
""",
|
""",
|
||||||
{"key": key},
|
{"key": key},
|
||||||
Wallet,
|
Wallet,
|
||||||
@@ -250,8 +253,11 @@ async def get_base_wallet_for_key(
|
|||||||
) -> BaseWallet | None:
|
) -> BaseWallet | None:
|
||||||
wallet = await (conn or db).fetchone(
|
wallet = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT id, "user", wallet_type, adminkey, inkey FROM wallets
|
SELECT wallets.id, "user", wallet_type, adminkey, inkey FROM wallets
|
||||||
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
|
INNER JOIN accounts ON wallets.user = accounts.id
|
||||||
|
WHERE (adminkey = :key OR inkey = :key)
|
||||||
|
AND deleted = false
|
||||||
|
AND accounts.activated = true
|
||||||
""",
|
""",
|
||||||
{"key": key},
|
{"key": key},
|
||||||
BaseWallet,
|
BaseWallet,
|
||||||
@@ -294,8 +300,14 @@ async def get_total_balance(conn: Connection | None = None):
|
|||||||
return row.get("balance", 0) or 0
|
return row.get("balance", 0) or 0
|
||||||
|
|
||||||
|
|
||||||
def _clear_wallet_cache(wallet_id):
|
def clear_wallet_id_cache(wallet_id: str):
|
||||||
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
||||||
if cached_wallet:
|
if cached_wallet:
|
||||||
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
||||||
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
|
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
|
||||||
|
|
||||||
|
|
||||||
|
def clear_wallet_cache(wallet: Wallet):
|
||||||
|
cache.pop(f"auth:wallet:{wallet.id}")
|
||||||
|
cache.pop(f"auth:x-api-key:{wallet.adminkey}")
|
||||||
|
cache.pop(f"auth:x-api-key:{wallet.inkey}")
|
||||||
|
|||||||
@@ -848,3 +848,19 @@ async def m042_index_accounts(db: Connection):
|
|||||||
CREATE INDEX IF NOT EXISTS idx_accounts_{index} ON accounts ("{index}");
|
CREATE INDEX IF NOT EXISTS idx_accounts_{index} ON accounts ("{index}");
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def m043_add_ui_customization_to_accounts(db: Connection):
|
||||||
|
"""
|
||||||
|
Adds ui_customization column to accounts.
|
||||||
|
Used for server side persistence of UI customization settings.
|
||||||
|
"""
|
||||||
|
await db.execute("ALTER TABLE accounts ADD COLUMN ui_customization TEXT")
|
||||||
|
|
||||||
|
|
||||||
|
async def m044_add_activated_to_accounts(db: Connection):
|
||||||
|
"""
|
||||||
|
Adds activated column to accounts.
|
||||||
|
Used for account activation status.
|
||||||
|
"""
|
||||||
|
await db.execute("ALTER TABLE accounts ADD COLUMN activated BOOLEAN DEFAULT true")
|
||||||
|
|||||||
@@ -6,23 +6,16 @@ from typing import Literal
|
|||||||
|
|
||||||
from fastapi import Query
|
from fastapi import Query
|
||||||
from lnurl import LnurlWithdrawResponse
|
from lnurl import LnurlWithdrawResponse
|
||||||
|
from loguru import logger
|
||||||
from pydantic import BaseModel, Field, validator
|
from pydantic import BaseModel, Field, validator
|
||||||
|
|
||||||
from lnbits.db import FilterModel
|
from lnbits.db import FilterModel
|
||||||
from lnbits.fiat import get_fiat_provider
|
|
||||||
from lnbits.fiat.base import (
|
from lnbits.fiat.base import (
|
||||||
FiatPaymentFailedStatus,
|
|
||||||
FiatPaymentPendingStatus,
|
|
||||||
FiatPaymentStatus,
|
FiatPaymentStatus,
|
||||||
FiatPaymentSuccessStatus,
|
|
||||||
)
|
)
|
||||||
from lnbits.utils.exchange_rates import allowed_currencies
|
from lnbits.utils.exchange_rates import allowed_currencies
|
||||||
from lnbits.wallets import get_funding_source
|
|
||||||
from lnbits.wallets.base import (
|
from lnbits.wallets.base import (
|
||||||
PaymentFailedStatus,
|
|
||||||
PaymentPendingStatus,
|
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
PaymentSuccessStatus,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -130,59 +123,23 @@ class Payment(BaseModel):
|
|||||||
"fiat_"
|
"fiat_"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# DEPRECATED: in v1.5.0, use service check_payment_status instead
|
||||||
async def check_status(
|
async def check_status(
|
||||||
self, skip_internal_payment_notifications: bool | None = False
|
self, skip_internal_payment_notifications: bool | None = False
|
||||||
) -> PaymentStatus:
|
) -> PaymentStatus:
|
||||||
if self.is_internal:
|
logger.warning("payment.check_status() is deprecated.")
|
||||||
if self.success:
|
from lnbits.core.services.payments import check_payment_status
|
||||||
return PaymentSuccessStatus()
|
|
||||||
if self.failed:
|
|
||||||
return PaymentFailedStatus()
|
|
||||||
if self.is_in and self.fiat_provider:
|
|
||||||
fiat_status = await self.check_fiat_status(
|
|
||||||
skip_internal_payment_notifications
|
|
||||||
)
|
|
||||||
return PaymentStatus(paid=fiat_status.paid)
|
|
||||||
return PaymentPendingStatus()
|
|
||||||
funding_source = get_funding_source()
|
|
||||||
if self.is_out:
|
|
||||||
status = await funding_source.get_payment_status(self.checking_id)
|
|
||||||
else:
|
|
||||||
status = await funding_source.get_invoice_status(self.checking_id)
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
return await check_payment_status(self, skip_internal_payment_notifications)
|
||||||
|
|
||||||
|
# DEPRECATED: in v1.5.0, use service check_payment_status instead
|
||||||
async def check_fiat_status(
|
async def check_fiat_status(
|
||||||
self, skip_internal_payment_notifications: bool | None = False
|
self, skip_internal_payment_notifications: bool | None = False
|
||||||
) -> FiatPaymentStatus:
|
) -> FiatPaymentStatus:
|
||||||
if not self.is_internal:
|
logger.warning("payment.check_fiat_status() is deprecated.")
|
||||||
return FiatPaymentPendingStatus()
|
from lnbits.core.services.fiat_providers import check_fiat_status
|
||||||
if self.success:
|
|
||||||
return FiatPaymentSuccessStatus()
|
|
||||||
if self.failed:
|
|
||||||
return FiatPaymentFailedStatus()
|
|
||||||
|
|
||||||
if not self.fiat_provider:
|
return await check_fiat_status(self, skip_internal_payment_notifications)
|
||||||
return FiatPaymentPendingStatus()
|
|
||||||
|
|
||||||
checking_id = self.extra.get("fiat_checking_id")
|
|
||||||
if not checking_id:
|
|
||||||
return FiatPaymentPendingStatus()
|
|
||||||
|
|
||||||
fiat_provider = await get_fiat_provider(self.fiat_provider)
|
|
||||||
if not fiat_provider:
|
|
||||||
return FiatPaymentPendingStatus()
|
|
||||||
fiat_status = await fiat_provider.get_invoice_status(checking_id)
|
|
||||||
|
|
||||||
if skip_internal_payment_notifications:
|
|
||||||
return fiat_status
|
|
||||||
|
|
||||||
if fiat_status.success:
|
|
||||||
# notify receivers asynchronously
|
|
||||||
from lnbits.tasks import internal_invoice_queue
|
|
||||||
|
|
||||||
await internal_invoice_queue.put(self.checking_id)
|
|
||||||
|
|
||||||
return fiat_status
|
|
||||||
|
|
||||||
|
|
||||||
class PaymentFilters(FilterModel):
|
class PaymentFilters(FilterModel):
|
||||||
|
|||||||
@@ -181,12 +181,14 @@ class AccountId(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Account(AccountId):
|
class Account(AccountId):
|
||||||
|
activated: bool = True
|
||||||
external_id: str | None = None # for external account linking
|
external_id: str | None = None # for external account linking
|
||||||
username: str | None = None
|
username: str | None = None
|
||||||
password_hash: str | None = None
|
password_hash: str | None = None
|
||||||
pubkey: str | None = None
|
pubkey: str | None = None
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
extra: UserExtra = UserExtra()
|
extra: UserExtra = UserExtra()
|
||||||
|
ui_customization: dict = Field(default_factory=dict)
|
||||||
|
|
||||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
updated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||||
@@ -240,6 +242,7 @@ class Account(AccountId):
|
|||||||
|
|
||||||
|
|
||||||
class AccountOverview(Account):
|
class AccountOverview(Account):
|
||||||
|
activated: bool = True
|
||||||
transaction_count: int | None = 0
|
transaction_count: int | None = 0
|
||||||
wallet_count: int | None = 0
|
wallet_count: int | None = 0
|
||||||
balance_msat: int | None = 0
|
balance_msat: int | None = 0
|
||||||
@@ -275,6 +278,7 @@ class AccountFilters(FilterModel):
|
|||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
|
activated: bool = True
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
email: str | None = None
|
email: str | None = None
|
||||||
@@ -288,6 +292,7 @@ class User(BaseModel):
|
|||||||
fiat_providers: list[str] = []
|
fiat_providers: list[str] = []
|
||||||
has_password: bool = False
|
has_password: bool = False
|
||||||
extra: UserExtra = UserExtra()
|
extra: UserExtra = UserExtra()
|
||||||
|
ui_customization: dict = Field(default_factory=dict)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def wallet_ids(self) -> list[str]:
|
def wallet_ids(self) -> list[str]:
|
||||||
@@ -313,6 +318,7 @@ class RegisterUser(BaseModel):
|
|||||||
username: str = Query(default=..., min_length=2, max_length=20)
|
username: str = Query(default=..., min_length=2, max_length=20)
|
||||||
password: str = Query(default=..., min_length=8, max_length=50)
|
password: str = Query(default=..., min_length=8, max_length=50)
|
||||||
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
||||||
|
invitation_code: str | None = Query(default=None, max_length=256)
|
||||||
|
|
||||||
|
|
||||||
class CreateUser(BaseModel):
|
class CreateUser(BaseModel):
|
||||||
@@ -356,6 +362,7 @@ class UpdateSuperuserPassword(BaseModel):
|
|||||||
username: str = Query(default=..., min_length=2, max_length=20)
|
username: str = Query(default=..., min_length=2, max_length=20)
|
||||||
password: str = Query(default=..., min_length=8, max_length=50)
|
password: str = Query(default=..., min_length=8, max_length=50)
|
||||||
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
||||||
|
first_install_token: str | None = Query(None)
|
||||||
|
|
||||||
|
|
||||||
class LoginUsr(BaseModel):
|
class LoginUsr(BaseModel):
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from .fiat_providers import check_fiat_status
|
||||||
from .funding_source import (
|
from .funding_source import (
|
||||||
get_balance_delta,
|
get_balance_delta,
|
||||||
switch_to_voidwallet,
|
switch_to_voidwallet,
|
||||||
@@ -7,6 +8,7 @@ from .notifications import enqueue_admin_notification, send_payment_notification
|
|||||||
from .payments import (
|
from .payments import (
|
||||||
calculate_fiat_amounts,
|
calculate_fiat_amounts,
|
||||||
cancel_hold_invoice,
|
cancel_hold_invoice,
|
||||||
|
check_payment_status,
|
||||||
check_transaction_status,
|
check_transaction_status,
|
||||||
check_wallet_limits,
|
check_wallet_limits,
|
||||||
create_fiat_invoice,
|
create_fiat_invoice,
|
||||||
@@ -40,6 +42,8 @@ __all__ = [
|
|||||||
"calculate_fiat_amounts",
|
"calculate_fiat_amounts",
|
||||||
"cancel_hold_invoice",
|
"cancel_hold_invoice",
|
||||||
"check_admin_settings",
|
"check_admin_settings",
|
||||||
|
"check_fiat_status",
|
||||||
|
"check_payment_status",
|
||||||
"check_transaction_status",
|
"check_transaction_status",
|
||||||
"check_wallet_limits",
|
"check_wallet_limits",
|
||||||
"check_webpush_settings",
|
"check_webpush_settings",
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ from lnbits.core.models import CreatePayment, Payment, PaymentState
|
|||||||
from lnbits.core.models.misc import SimpleStatus
|
from lnbits.core.models.misc import SimpleStatus
|
||||||
from lnbits.db import Connection
|
from lnbits.db import Connection
|
||||||
from lnbits.fiat import get_fiat_provider
|
from lnbits.fiat import get_fiat_provider
|
||||||
|
from lnbits.fiat.base import (
|
||||||
|
FiatPaymentFailedStatus,
|
||||||
|
FiatPaymentPendingStatus,
|
||||||
|
FiatPaymentStatus,
|
||||||
|
FiatPaymentSuccessStatus,
|
||||||
|
)
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
@@ -29,6 +35,40 @@ async def handle_fiat_payment_confirmation(
|
|||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_fiat_status(
|
||||||
|
payment: Payment, skip_internal_payment_notifications: bool | None = False
|
||||||
|
) -> FiatPaymentStatus:
|
||||||
|
if not payment.is_internal:
|
||||||
|
return FiatPaymentPendingStatus()
|
||||||
|
if payment.success:
|
||||||
|
return FiatPaymentSuccessStatus()
|
||||||
|
if payment.failed:
|
||||||
|
return FiatPaymentFailedStatus()
|
||||||
|
|
||||||
|
if not payment.fiat_provider:
|
||||||
|
return FiatPaymentPendingStatus()
|
||||||
|
|
||||||
|
checking_id = payment.extra.get("fiat_checking_id")
|
||||||
|
if not checking_id:
|
||||||
|
return FiatPaymentPendingStatus()
|
||||||
|
|
||||||
|
fiat_provider = await get_fiat_provider(payment.fiat_provider)
|
||||||
|
if not fiat_provider:
|
||||||
|
return FiatPaymentPendingStatus()
|
||||||
|
fiat_status = await fiat_provider.get_invoice_status(checking_id)
|
||||||
|
|
||||||
|
if skip_internal_payment_notifications:
|
||||||
|
return fiat_status
|
||||||
|
|
||||||
|
if fiat_status.success:
|
||||||
|
# notify receivers asynchronously
|
||||||
|
from lnbits.tasks import internal_invoice_queue
|
||||||
|
|
||||||
|
await internal_invoice_queue.put(payment.checking_id)
|
||||||
|
|
||||||
|
return fiat_status
|
||||||
|
|
||||||
|
|
||||||
def check_stripe_signature(
|
def check_stripe_signature(
|
||||||
payload: bytes,
|
payload: bytes,
|
||||||
sig_header: str | None,
|
sig_header: str | None,
|
||||||
@@ -78,8 +118,8 @@ async def verify_paypal_webhook(headers, payload: bytes):
|
|||||||
"""
|
"""
|
||||||
webhook_id = settings.paypal_webhook_id
|
webhook_id = settings.paypal_webhook_id
|
||||||
if not webhook_id:
|
if not webhook_id:
|
||||||
logger.warning("PayPal webhook ID not set; skipping verification.")
|
logger.warning("PayPal webhook ID not set.")
|
||||||
return
|
raise ValueError("PayPal webhook cannot be verified. Missing webhook ID.")
|
||||||
|
|
||||||
required_headers = {
|
required_headers = {
|
||||||
"PAYPAL-TRANSMISSION-ID": headers.get("PAYPAL-TRANSMISSION-ID"),
|
"PAYPAL-TRANSMISSION-ID": headers.get("PAYPAL-TRANSMISSION-ID"),
|
||||||
@@ -89,8 +129,8 @@ async def verify_paypal_webhook(headers, payload: bytes):
|
|||||||
"PAYPAL-AUTH-ALGO": headers.get("PAYPAL-AUTH-ALGO"),
|
"PAYPAL-AUTH-ALGO": headers.get("PAYPAL-AUTH-ALGO"),
|
||||||
}
|
}
|
||||||
if not all(required_headers.values()):
|
if not all(required_headers.values()):
|
||||||
logger.warning("Missing PayPal webhook headers; skipping verification.")
|
logger.warning("Missing PayPal webhook headers.")
|
||||||
return
|
raise ValueError("PayPal webhook cannot be verified. Missing headers.")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(base_url=settings.paypal_api_endpoint) as client:
|
async with httpx.AsyncClient(base_url=settings.paypal_api_endpoint) as client:
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from lnbits.utils.exchange_rates import fiat_amount_as_satoshis, satoshis_amount
|
|||||||
from lnbits.wallets import fake_wallet, get_funding_source
|
from lnbits.wallets import fake_wallet, get_funding_source
|
||||||
from lnbits.wallets.base import (
|
from lnbits.wallets.base import (
|
||||||
InvoiceResponse,
|
InvoiceResponse,
|
||||||
|
PaymentFailedStatus,
|
||||||
PaymentPendingStatus,
|
PaymentPendingStatus,
|
||||||
PaymentResponse,
|
PaymentResponse,
|
||||||
PaymentStatus,
|
PaymentStatus,
|
||||||
@@ -47,6 +48,7 @@ from ..models import (
|
|||||||
PaymentState,
|
PaymentState,
|
||||||
Wallet,
|
Wallet,
|
||||||
)
|
)
|
||||||
|
from .fiat_providers import check_fiat_status
|
||||||
from .notifications import send_payment_notification_in_background
|
from .notifications import send_payment_notification_in_background
|
||||||
|
|
||||||
payment_lock = asyncio.Lock()
|
payment_lock = asyncio.Lock()
|
||||||
@@ -364,7 +366,7 @@ async def update_pending_payments(wallet_id: str):
|
|||||||
async def update_pending_payment(
|
async def update_pending_payment(
|
||||||
payment: Payment, conn: Connection | None = None
|
payment: Payment, conn: Connection | None = None
|
||||||
) -> Payment:
|
) -> Payment:
|
||||||
status = await payment.check_status()
|
status = await check_payment_status(payment)
|
||||||
if status.failed:
|
if status.failed:
|
||||||
payment.status = PaymentState.FAILED
|
payment.status = PaymentState.FAILED
|
||||||
await update_payment(payment, conn=conn)
|
await update_payment(payment, conn=conn)
|
||||||
@@ -618,7 +620,29 @@ async def check_transaction_status(
|
|||||||
if payment.status == PaymentState.SUCCESS.value:
|
if payment.status == PaymentState.SUCCESS.value:
|
||||||
return PaymentSuccessStatus(fee_msat=payment.fee)
|
return PaymentSuccessStatus(fee_msat=payment.fee)
|
||||||
|
|
||||||
return await payment.check_status()
|
return await check_payment_status(payment)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_payment_status(
|
||||||
|
payment: Payment, skip_internal_payment_notifications: bool | None = False
|
||||||
|
) -> PaymentStatus:
|
||||||
|
if payment.is_internal:
|
||||||
|
if payment.success:
|
||||||
|
return PaymentSuccessStatus()
|
||||||
|
if payment.failed:
|
||||||
|
return PaymentFailedStatus()
|
||||||
|
if payment.is_in and payment.fiat_provider:
|
||||||
|
fiat_status = await check_fiat_status(
|
||||||
|
payment, skip_internal_payment_notifications
|
||||||
|
)
|
||||||
|
return PaymentStatus(paid=fiat_status.paid)
|
||||||
|
return PaymentPendingStatus()
|
||||||
|
funding_source = get_funding_source()
|
||||||
|
if payment.is_out:
|
||||||
|
status = await funding_source.get_payment_status(payment.checking_id)
|
||||||
|
else:
|
||||||
|
status = await funding_source.get_invoice_status(payment.checking_id)
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
async def get_payments_daily_stats(
|
async def get_payments_daily_stats(
|
||||||
@@ -752,7 +776,7 @@ async def _pay_internal_invoice(
|
|||||||
await update_payment(internal_payment, conn=conn)
|
await update_payment(internal_payment, conn=conn)
|
||||||
logger.success(f"internal payment successful {internal_payment.checking_id}")
|
logger.success(f"internal payment successful {internal_payment.checking_id}")
|
||||||
|
|
||||||
send_payment_notification_in_background(wallet, payment)
|
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
|
||||||
|
|
||||||
# notify receiver asynchronously
|
# notify receiver asynchronously
|
||||||
from lnbits.tasks import internal_invoice_queue
|
from lnbits.tasks import internal_invoice_queue
|
||||||
@@ -825,7 +849,8 @@ async def _pay_external_invoice(
|
|||||||
payment = await update_payment_success_status(
|
payment = await update_payment_success_status(
|
||||||
payment, payment_response, conn=conn
|
payment, payment_response, conn=conn
|
||||||
)
|
)
|
||||||
send_payment_notification_in_background(wallet, payment)
|
|
||||||
|
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
|
||||||
logger.success(f"payment successful {payment_response.checking_id}")
|
logger.success(f"payment successful {payment_response.checking_id}")
|
||||||
|
|
||||||
payment.checking_id = payment_response.checking_id
|
payment.checking_id = payment_response.checking_id
|
||||||
@@ -868,7 +893,7 @@ async def _verify_external_payment(
|
|||||||
raise PaymentError("Payment already paid.", status="success")
|
raise PaymentError("Payment already paid.", status="success")
|
||||||
|
|
||||||
# payment failed
|
# payment failed
|
||||||
status = await payment.check_status()
|
status = await check_payment_status(payment)
|
||||||
if status.failed:
|
if status.failed:
|
||||||
raise PaymentError(
|
raise PaymentError(
|
||||||
"Payment is failed node, retrying is not possible.", status="failed"
|
"Payment is failed node, retrying is not possible.", status="failed"
|
||||||
@@ -1033,3 +1058,13 @@ async def cancel_hold_invoice(payment: Payment) -> InvoiceResponse:
|
|||||||
await update_payment(payment)
|
await update_payment(payment)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
async def _send_payment_notification_in_background(
|
||||||
|
wallet_id: str, payment: Payment, conn: Connection | None = None
|
||||||
|
):
|
||||||
|
# fetch balance again
|
||||||
|
wallet = await get_wallet(wallet_id, conn=conn)
|
||||||
|
if not wallet:
|
||||||
|
raise PaymentError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
||||||
|
send_payment_notification_in_background(wallet, payment)
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from lnbits.core.crud.settings import set_settings_field
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
from lnbits.core.models.extensions import UserExtension
|
from lnbits.core.models.extensions import UserExtension
|
||||||
|
from lnbits.core.models.users import RegisterUser
|
||||||
from lnbits.db import Connection
|
from lnbits.db import Connection
|
||||||
from lnbits.settings import (
|
from lnbits.settings import (
|
||||||
EditableSettings,
|
EditableSettings,
|
||||||
@@ -192,3 +194,25 @@ async def init_admin_settings(super_user: str | None = None) -> SuperSettings:
|
|||||||
|
|
||||||
editable_settings = EditableSettings.from_dict(settings.dict())
|
editable_settings = EditableSettings.from_dict(settings.dict())
|
||||||
return await create_admin_settings(account.id, editable_settings.dict())
|
return await create_admin_settings(account.id, editable_settings.dict())
|
||||||
|
|
||||||
|
|
||||||
|
async def check_register_activation_settings(data: RegisterUser):
|
||||||
|
if not settings.lnbits_require_user_activation:
|
||||||
|
return None
|
||||||
|
if settings.lnbits_user_activation_by_invitation_code:
|
||||||
|
code = data.invitation_code.strip() if data.invitation_code else ""
|
||||||
|
if len(code) == 0:
|
||||||
|
raise ValueError("Invitation code cannot be empty.")
|
||||||
|
|
||||||
|
if code == settings.lnbits_register_reusable_activation_code:
|
||||||
|
return None
|
||||||
|
if code in settings.lnbits_register_one_time_activation_codes:
|
||||||
|
settings.lnbits_register_one_time_activation_codes.remove(code)
|
||||||
|
await set_settings_field(
|
||||||
|
"lnbits_register_one_time_activation_codes",
|
||||||
|
settings.lnbits_register_one_time_activation_codes,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
raise ValueError("Invalid invitation code.")
|
||||||
|
|
||||||
|
raise ValueError("No activation method provided.")
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ from lnbits.core.models.users import (
|
|||||||
UpdateAccessControlList,
|
UpdateAccessControlList,
|
||||||
)
|
)
|
||||||
from lnbits.core.services import create_user_account
|
from lnbits.core.services import create_user_account
|
||||||
from lnbits.core.services.users import update_user_account
|
from lnbits.core.services.users import (
|
||||||
|
check_register_activation_settings,
|
||||||
|
update_user_account,
|
||||||
|
)
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
access_token_payload,
|
access_token_payload,
|
||||||
check_account_exists,
|
check_account_exists,
|
||||||
@@ -86,6 +89,7 @@ async def login(data: LoginUsernamePassword) -> JSONResponse:
|
|||||||
account = await get_account_by_username_or_email(data.username)
|
account = await get_account_by_username_or_email(data.username)
|
||||||
if not account or not account.verify_password(data.password):
|
if not account or not account.verify_password(data.password):
|
||||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid credentials.")
|
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid credentials.")
|
||||||
|
|
||||||
return _auth_success_response(account.username, account.id, account.email)
|
return _auth_success_response(account.username, account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
@@ -94,7 +98,7 @@ async def nostr_login(request: Request) -> JSONResponse:
|
|||||||
if not settings.is_auth_method_allowed(AuthMethods.nostr_auth_nip98):
|
if not settings.is_auth_method_allowed(AuthMethods.nostr_auth_nip98):
|
||||||
raise HTTPException(HTTPStatus.FORBIDDEN, "Login with Nostr Auth not allowed.")
|
raise HTTPException(HTTPStatus.FORBIDDEN, "Login with Nostr Auth not allowed.")
|
||||||
event = _nostr_nip98_event(request)
|
event = _nostr_nip98_event(request)
|
||||||
account = await get_account_by_pubkey(event["pubkey"])
|
account = await get_account_by_pubkey(event["pubkey"], active_only=False)
|
||||||
if not account:
|
if not account:
|
||||||
account = Account(
|
account = Account(
|
||||||
id=uuid4().hex,
|
id=uuid4().hex,
|
||||||
@@ -102,6 +106,8 @@ async def nostr_login(request: Request) -> JSONResponse:
|
|||||||
extra=UserExtra(provider="nostr"),
|
extra=UserExtra(provider="nostr"),
|
||||||
)
|
)
|
||||||
await create_user_account(account)
|
await create_user_account(account)
|
||||||
|
if not account.activated:
|
||||||
|
raise HTTPException(HTTPStatus.UNAUTHORIZED, "User is not activated.")
|
||||||
return _auth_success_response(account.username or "", account.id, account.email)
|
return _auth_success_response(account.username or "", account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
@@ -357,12 +363,14 @@ async def register(data: RegisterUser) -> JSONResponse:
|
|||||||
if not is_valid_username(data.username):
|
if not is_valid_username(data.username):
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid username.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid username.")
|
||||||
|
|
||||||
if await get_account_by_username(data.username):
|
if await get_account_by_username(data.username, active_only=False):
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Username already exists.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Username already exists.")
|
||||||
|
|
||||||
if data.email and not is_valid_email_address(data.email):
|
if data.email and not is_valid_email_address(data.email):
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
||||||
|
|
||||||
|
await check_register_activation_settings(data)
|
||||||
|
|
||||||
account = Account(
|
account = Account(
|
||||||
id=uuid4().hex,
|
id=uuid4().hex,
|
||||||
email=data.email,
|
email=data.email,
|
||||||
@@ -467,7 +475,7 @@ async def reset_password(data: ResetUserPassword) -> JSONResponse:
|
|||||||
return _auth_success_response(account.username, user_id, account.email)
|
return _auth_success_response(account.username, user_id, account.email)
|
||||||
|
|
||||||
|
|
||||||
@auth_router.put("/update")
|
@auth_router.patch("")
|
||||||
async def update(
|
async def update(
|
||||||
data: UpdateUser, account: Account = Depends(check_account_exists)
|
data: UpdateUser, account: Account = Depends(check_account_exists)
|
||||||
) -> User | None:
|
) -> User | None:
|
||||||
@@ -483,13 +491,41 @@ async def update(
|
|||||||
return await get_user_from_account(account)
|
return await get_user_from_account(account)
|
||||||
|
|
||||||
|
|
||||||
|
@auth_router.patch("/ui")
|
||||||
|
async def update_ui_customization(
|
||||||
|
req: Request, account: Account = Depends(check_account_exists)
|
||||||
|
) -> Account:
|
||||||
|
ui_customization = await req.json()
|
||||||
|
account.ui_customization = {**(account.ui_customization or {}), **ui_customization}
|
||||||
|
|
||||||
|
if len(account.ui_customization or {}) > 1000 * 1024:
|
||||||
|
raise HTTPException(
|
||||||
|
HTTPStatus.BAD_REQUEST, "UI customization too large. Drop some fields."
|
||||||
|
)
|
||||||
|
|
||||||
|
await update_user_account(account)
|
||||||
|
return account
|
||||||
|
|
||||||
|
|
||||||
@auth_router.put("/first_install")
|
@auth_router.put("/first_install")
|
||||||
async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
||||||
if not settings.first_install:
|
if not settings.first_install:
|
||||||
raise HTTPException(HTTPStatus.FORBIDDEN, "This is not your first install")
|
raise HTTPException(HTTPStatus.FORBIDDEN, "This is not your first install")
|
||||||
|
|
||||||
|
if settings.first_install_token:
|
||||||
|
if not data.first_install_token:
|
||||||
|
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing first_install_token.")
|
||||||
|
if settings.first_install_token != data.first_install_token:
|
||||||
|
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid first_install_token.")
|
||||||
|
|
||||||
|
account = await get_account_by_username(data.username, False)
|
||||||
|
if account:
|
||||||
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Username already exists.")
|
||||||
|
|
||||||
account = await get_account(settings.super_user)
|
account = await get_account(settings.super_user)
|
||||||
if not account:
|
if not account:
|
||||||
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Superuser not found.")
|
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Superuser not found.")
|
||||||
|
|
||||||
account.username = data.username
|
account.username = data.username
|
||||||
account.extra = account.extra or UserExtra()
|
account.extra = account.extra or UserExtra()
|
||||||
account.extra.provider = "lnbits"
|
account.extra.provider = "lnbits"
|
||||||
@@ -505,7 +541,7 @@ async def _handle_sso_login(userinfo: OpenID, verified_user_id: str | None = Non
|
|||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid email.")
|
||||||
|
|
||||||
redirect_path = "/wallet"
|
redirect_path = "/wallet"
|
||||||
account = await get_account_by_email(email)
|
account = await get_account_by_email(email, active_only=False)
|
||||||
|
|
||||||
if verified_user_id:
|
if verified_user_id:
|
||||||
if account:
|
if account:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from lnbits.core.crud.payments import (
|
|||||||
from lnbits.core.models.misc import SimpleStatus
|
from lnbits.core.models.misc import SimpleStatus
|
||||||
from lnbits.core.models.payments import CreateInvoice
|
from lnbits.core.models.payments import CreateInvoice
|
||||||
from lnbits.core.services.fiat_providers import (
|
from lnbits.core.services.fiat_providers import (
|
||||||
|
check_fiat_status,
|
||||||
check_stripe_signature,
|
check_stripe_signature,
|
||||||
verify_paypal_webhook,
|
verify_paypal_webhook,
|
||||||
)
|
)
|
||||||
@@ -23,7 +24,7 @@ callback_router = APIRouter(prefix="/api/v1/callback", tags=["callback"])
|
|||||||
async def api_generic_webhook_handler(
|
async def api_generic_webhook_handler(
|
||||||
provider_name: str, request: Request
|
provider_name: str, request: Request
|
||||||
) -> SimpleStatus:
|
) -> SimpleStatus:
|
||||||
|
logger.info(f"Received callback from provider: '{provider_name}'.")
|
||||||
if provider_name.lower() == "stripe":
|
if provider_name.lower() == "stripe":
|
||||||
payload = await request.body()
|
payload = await request.body()
|
||||||
sig_header = request.headers.get("Stripe-Signature")
|
sig_header = request.headers.get("Stripe-Signature")
|
||||||
@@ -87,7 +88,7 @@ async def _handle_stripe_intent_session_completed(event: dict):
|
|||||||
if not payment:
|
if not payment:
|
||||||
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
||||||
return
|
return
|
||||||
await payment.check_fiat_status()
|
await check_fiat_status(payment)
|
||||||
|
|
||||||
|
|
||||||
async def _handle_stripe_checkout_session_completed(event: dict):
|
async def _handle_stripe_checkout_session_completed(event: dict):
|
||||||
@@ -110,7 +111,7 @@ async def _handle_stripe_checkout_session_completed(event: dict):
|
|||||||
payment = await get_standalone_payment(payment_hash)
|
payment = await get_standalone_payment(payment_hash)
|
||||||
if not payment:
|
if not payment:
|
||||||
raise ValueError(f"No payment found for hash: '{payment_hash}'.")
|
raise ValueError(f"No payment found for hash: '{payment_hash}'.")
|
||||||
await payment.check_fiat_status()
|
await check_fiat_status(payment)
|
||||||
|
|
||||||
|
|
||||||
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
||||||
@@ -155,7 +156,7 @@ async def _handle_stripe_subscription_invoice_paid(event: dict):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await payment.check_fiat_status()
|
await check_fiat_status(payment)
|
||||||
|
|
||||||
|
|
||||||
async def _get_stripe_subscription_payment_options(
|
async def _get_stripe_subscription_payment_options(
|
||||||
@@ -180,8 +181,10 @@ async def _get_stripe_subscription_payment_options(
|
|||||||
|
|
||||||
|
|
||||||
async def handle_paypal_event(event: dict):
|
async def handle_paypal_event(event: dict):
|
||||||
|
event_id = event.get("id", "")
|
||||||
event_type = event.get("event_type", "")
|
event_type = event.get("event_type", "")
|
||||||
resource = event.get("resource", {})
|
resource = event.get("resource", {})
|
||||||
|
logger.info(f"Handling PayPal event: '{event_id}'. Type: '{event_type}'.")
|
||||||
|
|
||||||
if event_type in ("CHECKOUT.ORDER.APPROVED", "PAYMENT.CAPTURE.COMPLETED"):
|
if event_type in ("CHECKOUT.ORDER.APPROVED", "PAYMENT.CAPTURE.COMPLETED"):
|
||||||
payment_hash = _paypal_extract_payment_hash(resource)
|
payment_hash = _paypal_extract_payment_hash(resource)
|
||||||
@@ -192,23 +195,20 @@ async def handle_paypal_event(event: dict):
|
|||||||
if not payment:
|
if not payment:
|
||||||
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
logger.warning(f"No payment found for hash: '{payment_hash}'.")
|
||||||
return
|
return
|
||||||
await payment.check_fiat_status()
|
await check_fiat_status(payment)
|
||||||
return
|
return
|
||||||
|
|
||||||
if event_type in (
|
if event_type in ("PAYMENT.SALE.COMPLETED"):
|
||||||
"PAYMENT.SALE.COMPLETED",
|
|
||||||
"BILLING.SUBSCRIPTION.PAYMENT.SUCCEEDED",
|
|
||||||
):
|
|
||||||
await _handle_paypal_subscription_payment(resource)
|
await _handle_paypal_subscription_payment(resource)
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.info(f"Unhandled PayPal event type: '{event_type}'.")
|
logger.warning(f"Unhandled PayPal event type: '{event_type}'.")
|
||||||
|
|
||||||
|
|
||||||
async def _handle_paypal_subscription_payment(resource: dict):
|
async def _handle_paypal_subscription_payment(resource: dict):
|
||||||
amount_info = resource.get("amount") or {}
|
amount_info = resource.get("amount") or {}
|
||||||
currency = (amount_info.get("currency_code") or "").upper()
|
currency = (amount_info.get("currency") or "").upper()
|
||||||
total = amount_info.get("value")
|
total = amount_info.get("total")
|
||||||
if not currency or total is None:
|
if not currency or total is None:
|
||||||
raise ValueError("PayPal subscription event missing amount.")
|
raise ValueError("PayPal subscription event missing amount.")
|
||||||
|
|
||||||
@@ -216,18 +216,14 @@ async def _handle_paypal_subscription_payment(resource: dict):
|
|||||||
if not custom_id:
|
if not custom_id:
|
||||||
raise ValueError("PayPal subscription event missing custom metadata.")
|
raise ValueError("PayPal subscription event missing custom metadata.")
|
||||||
|
|
||||||
try:
|
payment_options = _deserialize_paypal_metadata(custom_id)
|
||||||
metadata = json.loads(custom_id)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
metadata = {}
|
|
||||||
|
|
||||||
payment_options = FiatSubscriptionPaymentOptions(**metadata)
|
|
||||||
if not payment_options.wallet_id:
|
if not payment_options.wallet_id:
|
||||||
raise ValueError("PayPal subscription event missing wallet_id.")
|
raise ValueError("PayPal subscription event missing wallet_id.")
|
||||||
|
|
||||||
memo = payment_options.memo or ""
|
memo = payment_options.memo or ""
|
||||||
extra = {
|
extra = {
|
||||||
**(payment_options.extra or {}),
|
**(payment_options.extra or {}),
|
||||||
|
"subscription_request_id": resource.get("billing_agreement_id"),
|
||||||
"fiat_method": "subscription",
|
"fiat_method": "subscription",
|
||||||
"tag": payment_options.tag,
|
"tag": payment_options.tag,
|
||||||
"subscription": {
|
"subscription": {
|
||||||
@@ -247,7 +243,7 @@ async def _handle_paypal_subscription_payment(resource: dict):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await payment.check_fiat_status()
|
await check_fiat_status(payment)
|
||||||
|
|
||||||
|
|
||||||
def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
||||||
@@ -258,3 +254,29 @@ def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
|||||||
if pu.get("custom_id"):
|
if pu.get("custom_id"):
|
||||||
return pu.get("custom_id")
|
return pu.get("custom_id")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _deserialize_paypal_metadata(custom_id: str) -> FiatSubscriptionPaymentOptions:
|
||||||
|
try:
|
||||||
|
meta = json.loads(custom_id)
|
||||||
|
wallet_id = meta[0] if len(meta) > 0 else None
|
||||||
|
tag = meta[1] if len(meta) > 1 else None
|
||||||
|
subscription_request_id = meta[2] if len(meta) > 2 else None
|
||||||
|
extra_link = meta[3] if len(meta) > 3 else None
|
||||||
|
memo = meta[4] if len(meta) > 4 else None
|
||||||
|
|
||||||
|
extra = {
|
||||||
|
"link": extra_link,
|
||||||
|
"subscription_request_id": subscription_request_id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return FiatSubscriptionPaymentOptions(
|
||||||
|
wallet_id=wallet_id,
|
||||||
|
tag=tag,
|
||||||
|
subscription_request_id=subscription_request_id,
|
||||||
|
extra=extra,
|
||||||
|
memo=memo,
|
||||||
|
)
|
||||||
|
except (json.JSONDecodeError, IndexError) as e:
|
||||||
|
logger.warning(f"Failed to deserialize PayPal metadata: {e}")
|
||||||
|
return FiatSubscriptionPaymentOptions()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.models.misc import SimpleStatus
|
from lnbits.core.models.misc import SimpleStatus
|
||||||
from lnbits.core.models.wallets import WalletTypeInfo
|
from lnbits.core.models.wallets import WalletTypeInfo
|
||||||
@@ -30,6 +31,11 @@ async def create_subscription(
|
|||||||
data: CreateFiatSubscription,
|
data: CreateFiatSubscription,
|
||||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||||
) -> FiatSubscriptionResponse:
|
) -> FiatSubscriptionResponse:
|
||||||
|
logger.debug(
|
||||||
|
f"Creating subscription with provider '{provider}. '"
|
||||||
|
f"Subscription ID '{data.subscription_id}'."
|
||||||
|
)
|
||||||
|
|
||||||
fiat_provider = await get_fiat_provider(provider)
|
fiat_provider = await get_fiat_provider(provider)
|
||||||
if not fiat_provider:
|
if not fiat_provider:
|
||||||
raise HTTPException(404, "Fiat provider not found")
|
raise HTTPException(404, "Fiat provider not found")
|
||||||
@@ -47,6 +53,12 @@ async def create_subscription(
|
|||||||
subscription_response = await fiat_provider.create_subscription(
|
subscription_response = await fiat_provider.create_subscription(
|
||||||
data.subscription_id, data.quantity, data.payment_options
|
data.subscription_id, data.quantity, data.payment_options
|
||||||
)
|
)
|
||||||
|
if subscription_response.error_message:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to create subscription with provider '{provider}': "
|
||||||
|
f"{subscription_response.error_message}"
|
||||||
|
)
|
||||||
|
|
||||||
return subscription_response
|
return subscription_response
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ lnurl_router = APIRouter(tags=["LNURL"])
|
|||||||
|
|
||||||
async def _handle(lnurl: str) -> LnurlResponseModel:
|
async def _handle(lnurl: str) -> LnurlResponseModel:
|
||||||
try:
|
try:
|
||||||
|
if "@" in lnurl: # lower case lightning addresses
|
||||||
|
lnurl = lnurl.lower()
|
||||||
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
|
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
|
||||||
if isinstance(res, LnurlErrorResponse):
|
if isinstance(res, LnurlErrorResponse):
|
||||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from lnbits.core.crud import (
|
|||||||
update_admin_settings,
|
update_admin_settings,
|
||||||
update_wallet,
|
update_wallet,
|
||||||
)
|
)
|
||||||
|
from lnbits.core.crud.users import clear_user_id_cache, get_account, update_account
|
||||||
from lnbits.core.crud.wallets import delete_wallet_by_id
|
from lnbits.core.crud.wallets import delete_wallet_by_id
|
||||||
from lnbits.core.models import (
|
from lnbits.core.models import (
|
||||||
AccountFilters,
|
AccountFilters,
|
||||||
@@ -73,7 +74,7 @@ async def api_get_users(
|
|||||||
summary="Get user by Id",
|
summary="Get user by Id",
|
||||||
)
|
)
|
||||||
async def api_get_user(user_id: str) -> User:
|
async def api_get_user(user_id: str) -> User:
|
||||||
user = await get_user(user_id)
|
user = await get_user(user_id, active_only=False)
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(HTTPStatus.NOT_FOUND, "User not found.")
|
raise HTTPException(HTTPStatus.NOT_FOUND, "User not found.")
|
||||||
return user
|
return user
|
||||||
@@ -197,7 +198,7 @@ async def api_users_reset_password(user_id: str) -> str:
|
|||||||
return f"reset_key_{reset_key_b64}"
|
return f"reset_key_{reset_key_b64}"
|
||||||
|
|
||||||
|
|
||||||
@users_router.get(
|
@users_router.put(
|
||||||
"/user/{user_id}/admin",
|
"/user/{user_id}/admin",
|
||||||
dependencies=[Depends(check_super_user)],
|
dependencies=[Depends(check_super_user)],
|
||||||
name="Give or revoke admin permsisions to a user",
|
name="Give or revoke admin permsisions to a user",
|
||||||
@@ -220,6 +221,43 @@ async def api_users_toggle_admin(user_id: str) -> SimpleStatus:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@users_router.put(
|
||||||
|
"/user/{user_id}/activate",
|
||||||
|
name="Activate or deactivate a user",
|
||||||
|
)
|
||||||
|
async def api_users_toggle_activated(
|
||||||
|
user_id: str, admin_account: Account = Depends(check_admin)
|
||||||
|
) -> SimpleStatus:
|
||||||
|
if user_id == settings.super_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Cannot deactivate super user.",
|
||||||
|
)
|
||||||
|
if user_id == admin_account.id:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="You cannot deactivate yourself.",
|
||||||
|
)
|
||||||
|
|
||||||
|
if settings.is_admin_user(user_id):
|
||||||
|
settings.lnbits_admin_users.remove(user_id)
|
||||||
|
|
||||||
|
user_account = await get_account(user_id, active_only=False)
|
||||||
|
if not user_account:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
detail="User not found.",
|
||||||
|
)
|
||||||
|
user_account.activated = not user_account.activated
|
||||||
|
await update_account(user_account)
|
||||||
|
await clear_user_id_cache(user_id)
|
||||||
|
|
||||||
|
return SimpleStatus(
|
||||||
|
success=True,
|
||||||
|
message=f"User {'activated' if user_account.activated else 'deactivated'}.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@users_router.get("/user/{user_id}/wallet", name="Get wallets for user")
|
@users_router.get("/user/{user_id}/wallet", name="Get wallets for user")
|
||||||
async def api_users_get_user_wallet(user_id: str) -> list[Wallet]:
|
async def api_users_get_user_wallet(user_id: str) -> list[Wallet]:
|
||||||
return await get_wallets(user_id, deleted=None)
|
return await get_wallets(user_id, deleted=None)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from fastapi import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from lnbits.core.crud.wallets import (
|
from lnbits.core.crud.wallets import (
|
||||||
|
clear_wallet_cache,
|
||||||
create_wallet,
|
create_wallet,
|
||||||
get_wallets_paginated,
|
get_wallets_paginated,
|
||||||
)
|
)
|
||||||
@@ -37,7 +38,6 @@ from lnbits.decorators import (
|
|||||||
require_invoice_key,
|
require_invoice_key,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import generate_filter_params_openapi
|
from lnbits.helpers import generate_filter_params_openapi
|
||||||
from lnbits.utils.cache import cache
|
|
||||||
|
|
||||||
from ..crud import (
|
from ..crud import (
|
||||||
delete_wallet,
|
delete_wallet,
|
||||||
@@ -134,9 +134,7 @@ async def api_reset_wallet_keys(
|
|||||||
if not wallet or wallet.user != account_id.id:
|
if not wallet or wallet.user != account_id.id:
|
||||||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Wallet not found")
|
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Wallet not found")
|
||||||
|
|
||||||
cache.pop(f"auth:wallet:{wallet.id}")
|
clear_wallet_cache(wallet)
|
||||||
cache.pop(f"auth:x-api-key:{wallet.adminkey}")
|
|
||||||
cache.pop(f"auth:x-api-key:{wallet.inkey}")
|
|
||||||
|
|
||||||
wallet.adminkey = uuid4().hex
|
wallet.adminkey = uuid4().hex
|
||||||
wallet.inkey = uuid4().hex
|
wallet.inkey = uuid4().hex
|
||||||
|
|||||||
+9
-42
@@ -12,7 +12,6 @@ from typing import Any, Generic, Literal, TypeVar, get_origin
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, ValidationError, root_validator
|
from pydantic import BaseModel, ValidationError, root_validator
|
||||||
from sqlalchemy import event
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
|
||||||
from sqlalchemy.sql import text
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
@@ -56,14 +55,6 @@ def compat_timestamp_placeholder(key: str):
|
|||||||
return f":{key}"
|
return f":{key}"
|
||||||
|
|
||||||
|
|
||||||
def get_placeholder(model: Any, field: str) -> str:
|
|
||||||
type_ = model.__fields__[field].type_
|
|
||||||
if type_ == datetime:
|
|
||||||
return compat_timestamp_placeholder(field)
|
|
||||||
else:
|
|
||||||
return f":{field}"
|
|
||||||
|
|
||||||
|
|
||||||
class Compat:
|
class Compat:
|
||||||
type: str | None = "<inherited>"
|
type: str | None = "<inherited>"
|
||||||
schema: str | None = "<inherited>"
|
schema: str | None = "<inherited>"
|
||||||
@@ -326,31 +317,7 @@ class Database(Compat):
|
|||||||
self.engine: AsyncEngine = create_async_engine(
|
self.engine: AsyncEngine = create_async_engine(
|
||||||
database_uri, echo=settings.debug_database
|
database_uri, echo=settings.debug_database
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.type in {POSTGRES, COCKROACH}:
|
|
||||||
|
|
||||||
@event.listens_for(self.engine.sync_engine, "connect")
|
|
||||||
def register_custom_types(dbapi_connection, *_):
|
|
||||||
def _parse_date(value) -> datetime:
|
|
||||||
if value is None:
|
|
||||||
value = "1970-01-01 00:00:00"
|
|
||||||
f = "%Y-%m-%d %H:%M:%S.%f"
|
|
||||||
if "." not in value:
|
|
||||||
f = "%Y-%m-%d %H:%M:%S"
|
|
||||||
# Parse and add UTC timezone info
|
|
||||||
return datetime.strptime(value, f).replace(tzinfo=timezone.utc)
|
|
||||||
|
|
||||||
dbapi_connection.run_async(
|
|
||||||
lambda connection: connection.set_type_codec(
|
|
||||||
"TIMESTAMP",
|
|
||||||
encoder=datetime,
|
|
||||||
decoder=_parse_date,
|
|
||||||
schema="pg_catalog",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.lock = asyncio.Lock()
|
self.lock = asyncio.Lock()
|
||||||
|
|
||||||
logger.trace(f"database {self.type} added for {self.name}")
|
logger.trace(f"database {self.type} added for {self.name}")
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
@@ -663,7 +630,7 @@ def insert_query(table_name: str, model: BaseModel) -> str:
|
|||||||
placeholders = []
|
placeholders = []
|
||||||
keys = model_to_dict(model).keys()
|
keys = model_to_dict(model).keys()
|
||||||
for field in keys:
|
for field in keys:
|
||||||
placeholders.append(get_placeholder(model, field))
|
placeholders.append(f":{field}")
|
||||||
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
||||||
fields = ", ".join([f'"{key}"' for key in keys])
|
fields = ", ".join([f'"{key}"' for key in keys])
|
||||||
values = ", ".join(placeholders)
|
values = ", ".join(placeholders)
|
||||||
@@ -681,9 +648,8 @@ def update_query(
|
|||||||
"""
|
"""
|
||||||
fields = []
|
fields = []
|
||||||
for field in model_to_dict(model).keys():
|
for field in model_to_dict(model).keys():
|
||||||
placeholder = get_placeholder(model, field)
|
|
||||||
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
||||||
fields.append(f'"{field}" = {placeholder}')
|
fields.append(f'"{field}" = :{field}')
|
||||||
query = ", ".join(fields)
|
query = ", ".join(fields)
|
||||||
return f"UPDATE {table_name} SET {query} {where}" # noqa: S608
|
return f"UPDATE {table_name} SET {query} {where}" # noqa: S608
|
||||||
|
|
||||||
@@ -701,7 +667,12 @@ def model_to_dict(model: BaseModel) -> dict:
|
|||||||
if model.__fields__[key].field_info.extra.get("no_database", False):
|
if model.__fields__[key].field_info.extra.get("no_database", False):
|
||||||
continue
|
continue
|
||||||
if isinstance(value, datetime):
|
if isinstance(value, datetime):
|
||||||
_dict[key] = value.timestamp()
|
if DB_TYPE == SQLITE:
|
||||||
|
_dict[key] = value.timestamp()
|
||||||
|
else:
|
||||||
|
# remove tz. postgres and cockroach TIMESTAMP is not tz aware
|
||||||
|
# so it will throw if we dont remove the UTC.
|
||||||
|
_dict[key] = value.replace(tzinfo=None)
|
||||||
continue
|
continue
|
||||||
if (
|
if (
|
||||||
type(type_) is type(BaseModel)
|
type(type_) is type(BaseModel)
|
||||||
@@ -757,11 +728,7 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel: # noqa: C901
|
|||||||
if DB_TYPE == SQLITE:
|
if DB_TYPE == SQLITE:
|
||||||
_dict[key] = datetime.fromtimestamp(value, timezone.utc)
|
_dict[key] = datetime.fromtimestamp(value, timezone.utc)
|
||||||
else:
|
else:
|
||||||
# Ensure PostgreSQL datetime values have timezone info
|
_dict[key] = value.replace(tzinfo=timezone.utc)
|
||||||
if isinstance(value, datetime) and value.tzinfo is None:
|
|
||||||
_dict[key] = value.replace(tzinfo=timezone.utc)
|
|
||||||
else:
|
|
||||||
_dict[key] = value
|
|
||||||
continue
|
continue
|
||||||
if issubclass(type_, BaseModel):
|
if issubclass(type_, BaseModel):
|
||||||
_dict[key] = dict_to_submodel(type_, value)
|
_dict[key] = dict_to_submodel(type_, value)
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ async def check_account_id_exists(
|
|||||||
account_id,
|
account_id,
|
||||||
expiry=settings.auth_authentication_cache_minutes * 60,
|
expiry=settings.auth_authentication_cache_minutes * 60,
|
||||||
)
|
)
|
||||||
|
cache.set(f"auth:user:cache_key:{sha256s(account.id)}", cache_key)
|
||||||
|
|
||||||
return account_id
|
return account_id
|
||||||
|
|
||||||
|
|||||||
+63
-18
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from collections.abc import AsyncGenerator
|
from collections.abc import AsyncGenerator
|
||||||
from typing import Any
|
from typing import Any, Literal
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -24,6 +24,8 @@ from .base import (
|
|||||||
FiatSubscriptionResponse,
|
FiatSubscriptionResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FiatMethod = Literal["checkout", "subscription"]
|
||||||
|
|
||||||
|
|
||||||
class PayPalCheckoutOptions(BaseModel):
|
class PayPalCheckoutOptions(BaseModel):
|
||||||
class Config:
|
class Config:
|
||||||
@@ -34,11 +36,21 @@ class PayPalCheckoutOptions(BaseModel):
|
|||||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class PayPalSubscriptionOptions(BaseModel):
|
||||||
|
class Config:
|
||||||
|
extra = "ignore"
|
||||||
|
|
||||||
|
checking_id: str | None = None
|
||||||
|
payment_request: str | None = None
|
||||||
|
|
||||||
|
|
||||||
class PayPalCreateInvoiceOptions(BaseModel):
|
class PayPalCreateInvoiceOptions(BaseModel):
|
||||||
class Config:
|
class Config:
|
||||||
extra = "ignore"
|
extra = "ignore"
|
||||||
|
|
||||||
|
fiat_method: FiatMethod = "checkout"
|
||||||
checkout: PayPalCheckoutOptions | None = None
|
checkout: PayPalCheckoutOptions | None = None
|
||||||
|
subscription: PayPalSubscriptionOptions | None = None
|
||||||
|
|
||||||
|
|
||||||
class PayPalWallet(FiatProvider):
|
class PayPalWallet(FiatProvider):
|
||||||
@@ -96,6 +108,14 @@ class PayPalWallet(FiatProvider):
|
|||||||
opts = self._parse_create_opts(extra or {})
|
opts = self._parse_create_opts(extra or {})
|
||||||
if opts is None:
|
if opts is None:
|
||||||
return FiatInvoiceResponse(ok=False, error_message="Invalid PayPal options")
|
return FiatInvoiceResponse(ok=False, error_message="Invalid PayPal options")
|
||||||
|
if opts.fiat_method == "subscription":
|
||||||
|
term = opts.subscription or PayPalSubscriptionOptions()
|
||||||
|
checking_id = term.checking_id or urlsafe_short_hash()
|
||||||
|
return FiatInvoiceResponse(
|
||||||
|
ok=True,
|
||||||
|
checking_id=f"subscription_{checking_id}",
|
||||||
|
payment_request=term.payment_request or "",
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self._ensure_access_token()
|
await self._ensure_access_token()
|
||||||
@@ -171,6 +191,7 @@ class PayPalWallet(FiatProvider):
|
|||||||
or "https://lnbits.com"
|
or "https://lnbits.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.debug(f"Creating PayPal subscription with ID '{subscription_id}'")
|
||||||
if not payment_options.subscription_request_id:
|
if not payment_options.subscription_request_id:
|
||||||
payment_options.subscription_request_id = urlsafe_short_hash()
|
payment_options.subscription_request_id = urlsafe_short_hash()
|
||||||
payment_options.extra = payment_options.extra or {}
|
payment_options.extra = payment_options.extra or {}
|
||||||
@@ -182,7 +203,6 @@ class PayPalWallet(FiatProvider):
|
|||||||
await self._ensure_access_token()
|
await self._ensure_access_token()
|
||||||
payload = {
|
payload = {
|
||||||
"plan_id": subscription_id,
|
"plan_id": subscription_id,
|
||||||
"quantity": str(quantity),
|
|
||||||
"custom_id": self._serialize_metadata(payment_options),
|
"custom_id": self._serialize_metadata(payment_options),
|
||||||
"application_context": {
|
"application_context": {
|
||||||
"return_url": success_url,
|
"return_url": success_url,
|
||||||
@@ -205,7 +225,7 @@ class PayPalWallet(FiatProvider):
|
|||||||
return FiatSubscriptionResponse(
|
return FiatSubscriptionResponse(
|
||||||
ok=True,
|
ok=True,
|
||||||
checkout_session_url=approval_url,
|
checkout_session_url=approval_url,
|
||||||
subscription_request_id=payment_options.subscription_request_id,
|
subscription_request_id=data.get("id"),
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning(exc)
|
logger.warning(exc)
|
||||||
@@ -219,6 +239,10 @@ class PayPalWallet(FiatProvider):
|
|||||||
correlation_id: str,
|
correlation_id: str,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> FiatSubscriptionResponse:
|
) -> FiatSubscriptionResponse:
|
||||||
|
logger.debug(
|
||||||
|
f"Cancelling PayPal subscription '{subscription_id}'. "
|
||||||
|
f"Correlation ID '{correlation_id}'."
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
await self._ensure_access_token()
|
await self._ensure_access_token()
|
||||||
r = await self.client.post(
|
r = await self.client.post(
|
||||||
@@ -240,12 +264,20 @@ class PayPalWallet(FiatProvider):
|
|||||||
async def get_invoice_status(self, checking_id: str) -> FiatPaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> FiatPaymentStatus:
|
||||||
try:
|
try:
|
||||||
await self._ensure_access_token()
|
await self._ensure_access_token()
|
||||||
order_id = self._normalize_paypal_id(checking_id)
|
paypal_id = self._normalize_paypal_id(checking_id)
|
||||||
r = await self.client.get(
|
if paypal_id.startswith("subscription_"):
|
||||||
f"/v2/checkout/orders/{order_id}", headers=self._auth_headers()
|
capture_id = paypal_id.replace("subscription_", "", 1)
|
||||||
)
|
r = await self.client.get(
|
||||||
r.raise_for_status()
|
f"v2/payments/captures/{capture_id}", headers=self._auth_headers()
|
||||||
return self._status_from_order(r.json())
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return self._status_from_capture(r.json())
|
||||||
|
else:
|
||||||
|
r = await self.client.get(
|
||||||
|
f"/v2/checkout/orders/{paypal_id}", headers=self._auth_headers()
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return self._status_from_order(r.json())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.debug(f"Error getting PayPal order status: {exc}")
|
logger.debug(f"Error getting PayPal order status: {exc}")
|
||||||
return FiatPaymentPendingStatus()
|
return FiatPaymentPendingStatus()
|
||||||
@@ -270,6 +302,14 @@ class PayPalWallet(FiatProvider):
|
|||||||
return FiatPaymentFailedStatus()
|
return FiatPaymentFailedStatus()
|
||||||
return FiatPaymentPendingStatus()
|
return FiatPaymentPendingStatus()
|
||||||
|
|
||||||
|
def _status_from_capture(self, order: dict[str, Any]) -> FiatPaymentStatus:
|
||||||
|
status = (order.get("status") or "").upper()
|
||||||
|
if status in ["COMPLETED"]:
|
||||||
|
return FiatPaymentSuccessStatus()
|
||||||
|
if status in ["DECLINED", "FAILED", "CANCELLED", "CANCELED"]:
|
||||||
|
return FiatPaymentFailedStatus()
|
||||||
|
return FiatPaymentPendingStatus()
|
||||||
|
|
||||||
def _normalize_paypal_id(self, checking_id: str) -> str:
|
def _normalize_paypal_id(self, checking_id: str) -> str:
|
||||||
return (
|
return (
|
||||||
checking_id.replace("fiat_paypal_", "", 1)
|
checking_id.replace("fiat_paypal_", "", 1)
|
||||||
@@ -280,15 +320,20 @@ class PayPalWallet(FiatProvider):
|
|||||||
def _serialize_metadata(
|
def _serialize_metadata(
|
||||||
self, payment_options: FiatSubscriptionPaymentOptions
|
self, payment_options: FiatSubscriptionPaymentOptions
|
||||||
) -> str:
|
) -> str:
|
||||||
md = {
|
meta = [
|
||||||
"wallet_id": payment_options.wallet_id,
|
payment_options.wallet_id,
|
||||||
"memo": payment_options.memo,
|
payment_options.tag,
|
||||||
"extra": payment_options.extra,
|
payment_options.subscription_request_id,
|
||||||
"tag": payment_options.tag,
|
]
|
||||||
"subscription_request_id": payment_options.subscription_request_id,
|
if payment_options.extra:
|
||||||
}
|
meta.append(payment_options.extra.get("link", None))
|
||||||
raw = json.dumps(md)
|
|
||||||
return raw[:127] # PayPal custom_id limit
|
# Keep custom_id within PayPal's 127-char limit
|
||||||
|
memo_limit = 120 - len(json.dumps(meta))
|
||||||
|
if memo_limit > 0 and payment_options.memo:
|
||||||
|
meta.append(payment_options.memo[:memo_limit])
|
||||||
|
|
||||||
|
return json.dumps(meta)
|
||||||
|
|
||||||
def _parse_create_opts(
|
def _parse_create_opts(
|
||||||
self, raw_opts: dict[str, Any]
|
self, raw_opts: dict[str, Any]
|
||||||
|
|||||||
+10
-54
@@ -7,15 +7,14 @@ from typing import Any
|
|||||||
from urllib import request
|
from urllib import request
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import jwt
|
import jwt
|
||||||
import shortuuid
|
import shortuuid
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from packaging import version
|
from packaging import version
|
||||||
from pydantic.schema import field_schema
|
from pydantic.schema import field_schema
|
||||||
|
from starlette.templating import Jinja2Templates
|
||||||
|
|
||||||
from lnbits.jinja2_templating import Jinja2Templates
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.crypto import AESCipher
|
from lnbits.utils.crypto import AESCipher
|
||||||
from lnbits.utils.exchange_rates import currencies
|
from lnbits.utils.exchange_rates import currencies
|
||||||
@@ -66,61 +65,15 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
for f in additional_folders
|
for f in additional_folders
|
||||||
]
|
]
|
||||||
folders.extend(additional_folders)
|
folders.extend(additional_folders)
|
||||||
t = Jinja2Templates(loader=jinja2.FileSystemLoader(folders))
|
t = Jinja2Templates(directory=folders)
|
||||||
t.env.globals["static_url_for"] = static_url_for
|
t.env.globals["static_url_for"] = static_url_for
|
||||||
t.env.globals["normalize_path"] = normalize_path
|
t.env.globals["normalize_path"] = normalize_path
|
||||||
|
|
||||||
window_settings = {
|
# used in base.html
|
||||||
"AD_SPACE": settings.lnbits_ad_space,
|
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
||||||
"AD_SPACE_ENABLED": settings.lnbits_ad_space_enabled,
|
t.env.globals["LNBITS_APPLE_TOUCH_ICON"] = settings.lnbits_apple_touch_icon
|
||||||
"AD_SPACE_TITLE": settings.lnbits_ad_space_title,
|
t.env.globals["SETTINGS"] = settings.to_public().dict(by_alias=True)
|
||||||
"EXTENSIONS": list(settings.lnbits_installed_extensions_ids),
|
t.env.globals["CURRENCIES"] = list(currencies.keys())
|
||||||
"HIDE_API": settings.lnbits_hide_api,
|
|
||||||
"SITE_TITLE": settings.lnbits_site_title,
|
|
||||||
"SITE_TAGLINE": settings.lnbits_site_tagline,
|
|
||||||
"SITE_DESCRIPTION": settings.lnbits_site_description,
|
|
||||||
"LNBITS_ADMIN_UI": settings.lnbits_admin_ui,
|
|
||||||
"LNBITS_AUDIT_ENABLED": settings.lnbits_audit_enabled,
|
|
||||||
"LNBITS_AUTH_METHODS": settings.auth_allowed_methods,
|
|
||||||
"LNBITS_AUTH_KEYCLOAK_ORG": settings.keycloak_client_custom_org,
|
|
||||||
"LNBITS_AUTH_KEYCLOAK_ICON": settings.keycloak_client_custom_icon,
|
|
||||||
"LNBITS_CUSTOM_IMAGE": settings.lnbits_custom_image,
|
|
||||||
"LNBITS_CUSTOM_BADGE": settings.lnbits_custom_badge,
|
|
||||||
"LNBITS_CUSTOM_BADGE_COLOR": settings.lnbits_custom_badge_color,
|
|
||||||
"LNBITS_EXTENSIONS_DEACTIVATE_ALL": settings.lnbits_extensions_deactivate_all,
|
|
||||||
"LNBITS_NEW_ACCOUNTS_ALLOWED": settings.new_accounts_allowed,
|
|
||||||
"LNBITS_NODE_UI": settings.lnbits_node_ui and settings.has_nodemanager,
|
|
||||||
"LNBITS_NODE_UI_AVAILABLE": settings.has_nodemanager,
|
|
||||||
"LNBITS_QR_LOGO": settings.lnbits_qr_logo,
|
|
||||||
"LNBITS_APPLE_TOUCH_ICON": settings.lnbits_apple_touch_icon,
|
|
||||||
"LNBITS_SERVICE_FEE": settings.lnbits_service_fee,
|
|
||||||
"LNBITS_SERVICE_FEE_MAX": settings.lnbits_service_fee_max,
|
|
||||||
"LNBITS_SERVICE_FEE_WALLET": settings.lnbits_service_fee_wallet,
|
|
||||||
"LNBITS_SHOW_HOME_PAGE_ELEMENTS": settings.lnbits_show_home_page_elements,
|
|
||||||
"LNBITS_THEME_OPTIONS": settings.lnbits_theme_options,
|
|
||||||
"LNBITS_VERSION": settings.version,
|
|
||||||
"USE_CUSTOM_LOGO": settings.lnbits_custom_logo,
|
|
||||||
"LNBITS_DEFAULT_REACTION": settings.lnbits_default_reaction,
|
|
||||||
"LNBITS_DEFAULT_THEME": settings.lnbits_default_theme,
|
|
||||||
"LNBITS_DEFAULT_BORDER": settings.lnbits_default_border,
|
|
||||||
"LNBITS_DEFAULT_GRADIENT": settings.lnbits_default_gradient,
|
|
||||||
"LNBITS_DEFAULT_BGIMAGE": settings.lnbits_default_bgimage,
|
|
||||||
"VOIDWALLET": settings.lnbits_backend_wallet_class == "VoidWallet",
|
|
||||||
"WEBPUSH_PUBKEY": settings.lnbits_webpush_pubkey,
|
|
||||||
"LNBITS_EXTENSIONS_REVIEWS_URL": settings.lnbits_extensions_reviews_url,
|
|
||||||
"LNBITS_DENOMINATION": settings.lnbits_denomination,
|
|
||||||
"has_holdinvoice": settings.has_holdinvoice,
|
|
||||||
"LNBITS_NOSTR_CONFIGURED": settings.is_nostr_notifications_configured(),
|
|
||||||
"LNBITS_TELEGRAM_CONFIGURED": settings.is_telegram_notifications_configured(),
|
|
||||||
"LNBITS_EXT_BUILDER": settings.lnbits_extensions_builder_activate_non_admins,
|
|
||||||
"LNBITS_CURRENCIES": list(currencies.keys()),
|
|
||||||
"LNBITS_ALLOWED_CURRENCIES": settings.lnbits_allowed_currencies,
|
|
||||||
"CACHE_KEY": settings.server_startup_time,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.env.globals["WINDOW_SETTINGS"] = window_settings
|
|
||||||
for key, value in window_settings.items():
|
|
||||||
t.env.globals[key] = value
|
|
||||||
|
|
||||||
if settings.bundle_assets:
|
if settings.bundle_assets:
|
||||||
t.env.globals["INCLUDED_JS"] = ["bundle.min.js"]
|
t.env.globals["INCLUDED_JS"] = ["bundle.min.js"]
|
||||||
@@ -134,6 +87,9 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
t.env.globals["INCLUDED_CSS"] = vendor_files["css"]
|
t.env.globals["INCLUDED_CSS"] = vendor_files["css"]
|
||||||
t.env.globals["INCLUDED_COMPONENTS"] = vendor_files["components"]
|
t.env.globals["INCLUDED_COMPONENTS"] = vendor_files["components"]
|
||||||
|
|
||||||
|
# backwards compatibility for extensions (tpos)
|
||||||
|
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
|
||||||
|
|
||||||
return t
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import typing
|
|
||||||
|
|
||||||
from jinja2 import BaseLoader, Environment, pass_context
|
|
||||||
from starlette.datastructures import QueryParams
|
|
||||||
from starlette.requests import Request
|
|
||||||
from starlette.templating import Jinja2Templates as SuperJinja2Templates
|
|
||||||
|
|
||||||
|
|
||||||
class Jinja2Templates(SuperJinja2Templates):
|
|
||||||
def __init__(self, loader: BaseLoader) -> None:
|
|
||||||
self.env = self.get_environment(loader)
|
|
||||||
super().__init__(env=self.env)
|
|
||||||
|
|
||||||
def get_environment(self, loader: BaseLoader) -> Environment:
|
|
||||||
@pass_context
|
|
||||||
def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
|
|
||||||
request: Request = context["request"]
|
|
||||||
return request.app.url_path_for(name, **path_params)
|
|
||||||
|
|
||||||
def url_params_update(init: QueryParams, **new: typing.Any) -> QueryParams:
|
|
||||||
values = dict(init)
|
|
||||||
values.update(new)
|
|
||||||
return QueryParams(**values)
|
|
||||||
|
|
||||||
env = Environment(loader=loader, autoescape=True)
|
|
||||||
env.globals["url_for"] = url_for
|
|
||||||
env.globals["url_params_update"] = url_params_update
|
|
||||||
return env
|
|
||||||
@@ -76,7 +76,7 @@ class LndRestNode(Node):
|
|||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def get(self, path: str, **kwargs):
|
def get(self, path: str, **kwargs):
|
||||||
return self.request("GET", path, **kwargs)
|
return self.request("GET", path, timeout=30, **kwargs)
|
||||||
|
|
||||||
async def _get_id(self) -> str:
|
async def _get_id(self) -> str:
|
||||||
info = await self.get("/v1/getinfo")
|
info = await self.get("/v1/getinfo")
|
||||||
@@ -99,11 +99,12 @@ class LndRestNode(Node):
|
|||||||
"perm": True,
|
"perm": True,
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def disconnect_peer(self, peer_id: str):
|
async def disconnect_peer(self, peer_id: str):
|
||||||
try:
|
try:
|
||||||
await self.request("DELETE", "/v1/peers/" + peer_id)
|
await self.request("DELETE", "/v1/peers/" + peer_id, timeout=30)
|
||||||
except HTTPException as exc:
|
except HTTPException as exc:
|
||||||
if "unable to disconnect" in exc.detail:
|
if "unable to disconnect" in exc.detail:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -141,6 +142,7 @@ class LndRestNode(Node):
|
|||||||
"local_funding_amount": local_amount,
|
"local_funding_amount": local_amount,
|
||||||
"push_sat": push_amount,
|
"push_sat": push_amount,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
return ChannelPoint(
|
return ChannelPoint(
|
||||||
# WHY IS THIS REVERSED?!
|
# WHY IS THIS REVERSED?!
|
||||||
@@ -214,6 +216,7 @@ class LndRestNode(Node):
|
|||||||
# 'min_htlc_msat': <uint64>,
|
# 'min_htlc_msat': <uint64>,
|
||||||
# 'inbound_fee': <InboundFee>,
|
# 'inbound_fee': <InboundFee>,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_channel(self, channel_id: str) -> NodeChannel | None:
|
async def get_channel(self, channel_id: str) -> NodeChannel | None:
|
||||||
|
|||||||
+149
-5
@@ -41,6 +41,14 @@ class UsersSettings(LNbitsSettings):
|
|||||||
lnbits_admin_users: list[str] = Field(default=[])
|
lnbits_admin_users: list[str] = Field(default=[])
|
||||||
lnbits_allowed_users: list[str] = Field(default=[])
|
lnbits_allowed_users: list[str] = Field(default=[])
|
||||||
lnbits_allow_new_accounts: bool = Field(default=True)
|
lnbits_allow_new_accounts: bool = Field(default=True)
|
||||||
|
lnbits_require_user_activation: bool = Field(default=False)
|
||||||
|
|
||||||
|
lnbits_user_activation_by_email: bool = Field(default=False)
|
||||||
|
lnbits_user_activation_by_payment: bool = Field(default=False)
|
||||||
|
lnbits_user_activation_by_invitation_code: bool = Field(default=False)
|
||||||
|
|
||||||
|
lnbits_register_reusable_activation_code: str = Field(default="")
|
||||||
|
lnbits_register_one_time_activation_codes: list[str] = Field(default=[])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def new_accounts_allowed(self) -> bool:
|
def new_accounts_allowed(self) -> bool:
|
||||||
@@ -252,6 +260,11 @@ class ThemesSettings(LNbitsSettings):
|
|||||||
)
|
)
|
||||||
lnbits_show_home_page_elements: bool = Field(default=True)
|
lnbits_show_home_page_elements: bool = Field(default=True)
|
||||||
lnbits_default_wallet_name: str = Field(default="LNbits wallet")
|
lnbits_default_wallet_name: str = Field(default="LNbits wallet")
|
||||||
|
|
||||||
|
lnbits_wallet_featured_button_label: str | None = Field(default=None)
|
||||||
|
lnbits_wallet_featured_button_url: str | None = Field(default=None)
|
||||||
|
lnbits_wallet_featured_button_icon: str | None = Field(default=None)
|
||||||
|
|
||||||
lnbits_custom_badge: str | None = Field(default=None)
|
lnbits_custom_badge: str | None = Field(default=None)
|
||||||
lnbits_custom_badge_color: str = Field(default="warning")
|
lnbits_custom_badge_color: str = Field(default="warning")
|
||||||
lnbits_theme_options: list[str] = Field(
|
lnbits_theme_options: list[str] = Field(
|
||||||
@@ -279,10 +292,14 @@ class ThemesSettings(LNbitsSettings):
|
|||||||
lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png")
|
lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png")
|
||||||
lnbits_apple_touch_icon: str | None = Field(default=None)
|
lnbits_apple_touch_icon: str | None = Field(default=None)
|
||||||
lnbits_default_reaction: str = Field(default="confettiBothSides")
|
lnbits_default_reaction: str = Field(default="confettiBothSides")
|
||||||
lnbits_default_theme: str = Field(default="salvador")
|
lnbits_default_theme: str = Field(default="bitcoin")
|
||||||
lnbits_default_border: str = Field(default="hard-border")
|
lnbits_default_border: str = Field(default="hard-border")
|
||||||
lnbits_default_gradient: bool = Field(default=True)
|
lnbits_default_gradient: bool = Field(default=True)
|
||||||
lnbits_default_bgimage: str | None = Field(default=None)
|
lnbits_default_bgimage: str | None = Field(default=None)
|
||||||
|
lnbits_default_dark: bool = Field(default=True)
|
||||||
|
lnbits_default_card_rounded: bool = Field(default=True)
|
||||||
|
lnbits_default_card_gradient: bool = Field(default=True)
|
||||||
|
lnbits_default_card_shadow: bool = Field(default=False)
|
||||||
|
|
||||||
|
|
||||||
class OpsSettings(LNbitsSettings):
|
class OpsSettings(LNbitsSettings):
|
||||||
@@ -590,11 +607,10 @@ class SparkFundingSource(LNbitsSettings):
|
|||||||
|
|
||||||
|
|
||||||
class SparkL2FundingSource(LNbitsSettings):
|
class SparkL2FundingSource(LNbitsSettings):
|
||||||
spark_l2_mnemonic: str | None = Field(default=None)
|
|
||||||
spark_l2_network: str = Field(default="MAINNET")
|
spark_l2_network: str = Field(default="MAINNET")
|
||||||
spark_l2_internal_sidecar_version: str | None = Field(default="0.1.1")
|
spark_l2_external_endpoint: str | None = Field(default="http://localhost:8765")
|
||||||
spark_l2_external_endpoint: str | None = Field(default=None)
|
|
||||||
spark_l2_external_api_key: str | None = Field(default=None)
|
spark_l2_external_api_key: str | None = Field(default=None)
|
||||||
|
spark_l2_mnemonic: str | None = Field(default=None)
|
||||||
spark_l2_pay_wait_ms: int = Field(default=4000, ge=0)
|
spark_l2_pay_wait_ms: int = Field(default=4000, ge=0)
|
||||||
spark_l2_pay_poll_ms: int = Field(default=500, ge=0)
|
spark_l2_pay_poll_ms: int = Field(default=500, ge=0)
|
||||||
spark_l2_stream_keepalive_ms: int = Field(default=15000, ge=0)
|
spark_l2_stream_keepalive_ms: int = Field(default=15000, ge=0)
|
||||||
@@ -990,6 +1006,7 @@ class EnvSettings(LNbitsSettings):
|
|||||||
enable_log_to_file: bool = Field(default=True)
|
enable_log_to_file: bool = Field(default=True)
|
||||||
log_rotation: str = Field(default="100 MB")
|
log_rotation: str = Field(default="100 MB")
|
||||||
log_retention: str = Field(default="3 months")
|
log_retention: str = Field(default="3 months")
|
||||||
|
first_install_token: str | None = Field(default=None)
|
||||||
|
|
||||||
cleanup_wallets_days: int = Field(default=90, ge=0)
|
cleanup_wallets_days: int = Field(default=90, ge=0)
|
||||||
funding_source_max_retries: int = Field(default=4, ge=0)
|
funding_source_max_retries: int = Field(default=4, ge=0)
|
||||||
@@ -1033,7 +1050,7 @@ class SuperUserSettings(LNbitsSettings):
|
|||||||
"FakeWallet",
|
"FakeWallet",
|
||||||
"LNPayWallet",
|
"LNPayWallet",
|
||||||
"LNbitsWallet",
|
"LNbitsWallet",
|
||||||
"LightsparkSparkWallet",
|
"SparkL2Wallet",
|
||||||
"LnTipsWallet",
|
"LnTipsWallet",
|
||||||
"LndRestWallet",
|
"LndRestWallet",
|
||||||
"LndWallet",
|
"LndWallet",
|
||||||
@@ -1139,6 +1156,133 @@ class Settings(EditableSettings, ReadOnlySettings, TransientSettings, BaseSettin
|
|||||||
and ext_id in self.lnbits_all_extensions_ids
|
and ext_id in self.lnbits_all_extensions_ids
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def to_public(self) -> PublicSettings:
|
||||||
|
return PublicSettings.from_settings(self)
|
||||||
|
|
||||||
|
|
||||||
|
class PublicSettings(BaseModel):
|
||||||
|
"""
|
||||||
|
PublicSettings is used for templating and public information.
|
||||||
|
WARNING: do not expose private information, PublicSettings is exposed publicly.
|
||||||
|
"""
|
||||||
|
|
||||||
|
allow_register: bool = Field(alias="allowRegister")
|
||||||
|
qr_logo: str = Field(alias="qrLogo")
|
||||||
|
site_title: str = Field(alias="siteTitle")
|
||||||
|
site_tagline: str = Field(alias="siteTagline")
|
||||||
|
site_description: str | None = Field(alias="siteDescription")
|
||||||
|
auth_methods: list[str] = Field(alias="authMethods")
|
||||||
|
keycloak_org: str | None = Field(alias="keycloakOrg")
|
||||||
|
keycloak_icon: str | None = Field(alias="keycloakIcon")
|
||||||
|
has_holdinvoice: bool = Field(alias="hasHoldinvoice")
|
||||||
|
has_nodemanager: bool = Field(alias="hasNodemanager")
|
||||||
|
show_nodemanager: bool = Field(alias="showNodemanager")
|
||||||
|
custom_logo: str | None = Field(alias="customLogo")
|
||||||
|
show_voidwallet: bool = Field(alias="showVoidwallet")
|
||||||
|
version: str = Field(alias="version")
|
||||||
|
show_home_page_elements: bool = Field(alias="showHomePageElements")
|
||||||
|
hide_api: bool = Field(alias="hideApi")
|
||||||
|
cache_key: str = Field(alias="cacheKey")
|
||||||
|
webpush_pubkey: str | None = Field(alias="webpushPubkey")
|
||||||
|
show_extensions: bool = Field(alias="showExtensions")
|
||||||
|
show_audit: bool = Field(alias="showAudit")
|
||||||
|
show_admin: bool = Field(alias="showAdmin")
|
||||||
|
ad_space: list[list[str]] = Field(alias="adSpace")
|
||||||
|
ad_space_title: str = Field(alias="adSpaceTitle")
|
||||||
|
show_ad_space: bool = Field(alias="showAdSpace")
|
||||||
|
custom_image: str | None = Field(alias="customImage")
|
||||||
|
custom_badge: str | None = Field(alias="customBadge")
|
||||||
|
custom_badge_color: str | None = Field(alias="customBadgeColor")
|
||||||
|
service_fee: float = Field(alias="serviceFee")
|
||||||
|
service_fee_max: int = Field(alias="serviceFeeMax")
|
||||||
|
service_fee_wallet: str | None = Field(alias="serviceFeeWallet")
|
||||||
|
theme_options: list[str] = Field(alias="themeOptions")
|
||||||
|
default_reaction: str = Field(alias="defaultReaction")
|
||||||
|
default_theme: str = Field(alias="defaultTheme")
|
||||||
|
default_border: str = Field(alias="defaultBorder")
|
||||||
|
default_bgimage: str | None = Field(alias="defaultBgimage")
|
||||||
|
default_gradient: bool = Field(alias="defaultGradient")
|
||||||
|
default_dark: bool = Field(alias="defaultDark")
|
||||||
|
default_card_rounded: bool = Field(alias="defaultCardRounded")
|
||||||
|
default_card_gradient: bool = Field(alias="defaultCardGradient")
|
||||||
|
default_card_shadow: bool = Field(alias="defaultCardShadow")
|
||||||
|
denomination: str | None = Field()
|
||||||
|
extensions: list[str] = Field()
|
||||||
|
allowed_currencies: list[str] = Field(alias="allowedCurrencies")
|
||||||
|
extensions_reviews_url: str = Field(alias="extensionsReviewsUrl")
|
||||||
|
ext_builder: bool = Field(alias="extBuilder")
|
||||||
|
nostr_configured: bool = Field(alias="nostrConfigured")
|
||||||
|
telegram_configured: bool = Field(alias="telegramConfigured")
|
||||||
|
wallet_featured_button_label: str | None = Field(alias="walletFeaturedButtonLabel")
|
||||||
|
wallet_featured_button_url: str | None = Field(alias="walletFeaturedButtonUrl")
|
||||||
|
wallet_featured_button_icon: str | None = Field(alias="walletFeaturedButtonIcon")
|
||||||
|
lnbits_user_activation_by_email: bool = Field(alias="userActivationByEmail")
|
||||||
|
lnbits_user_activation_by_payment: bool = Field(alias="userActivationByPayment")
|
||||||
|
lnbits_user_activation_by_invitation_code: bool = Field(
|
||||||
|
alias="userActivationByInvitationCode"
|
||||||
|
)
|
||||||
|
has_first_install_token: bool = Field(alias="hasFirstInstallToken")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_settings(cls, settings: Settings):
|
||||||
|
ads = [x.split(";") for x in settings.lnbits_ad_space.split(",")]
|
||||||
|
return cls(
|
||||||
|
adSpace=ads,
|
||||||
|
adSpaceTitle=settings.lnbits_ad_space_title,
|
||||||
|
showAdSpace=settings.lnbits_ad_space_enabled and len(ads) > 0,
|
||||||
|
allowRegister=settings.new_accounts_allowed,
|
||||||
|
qrLogo=settings.lnbits_qr_logo,
|
||||||
|
siteDescription=settings.lnbits_site_description,
|
||||||
|
siteTitle=settings.lnbits_site_title,
|
||||||
|
siteTagline=settings.lnbits_site_tagline,
|
||||||
|
authMethods=settings.auth_allowed_methods,
|
||||||
|
keycloakOrg=settings.keycloak_client_custom_org,
|
||||||
|
keycloakIcon=settings.keycloak_client_custom_icon,
|
||||||
|
hasHoldinvoice=settings.has_holdinvoice,
|
||||||
|
hasNodemanager=settings.has_nodemanager,
|
||||||
|
showNodemanager=settings.lnbits_node_ui and settings.has_nodemanager,
|
||||||
|
customLogo=settings.lnbits_custom_logo,
|
||||||
|
showVoidwallet=settings.lnbits_backend_wallet_class == "VoidWallet",
|
||||||
|
version=settings.version,
|
||||||
|
showHomePageElements=settings.lnbits_show_home_page_elements,
|
||||||
|
hideApi=settings.lnbits_hide_api,
|
||||||
|
cacheKey=str(settings.server_startup_time),
|
||||||
|
webpushPubkey=settings.lnbits_webpush_pubkey,
|
||||||
|
showExtensions=not settings.lnbits_extensions_deactivate_all,
|
||||||
|
showAudit=settings.lnbits_audit_enabled,
|
||||||
|
showAdmin=settings.lnbits_admin_ui,
|
||||||
|
customImage=settings.lnbits_custom_image,
|
||||||
|
customBadge=settings.lnbits_custom_badge,
|
||||||
|
customBadgeColor=settings.lnbits_custom_badge_color,
|
||||||
|
serviceFee=settings.lnbits_service_fee,
|
||||||
|
serviceFeeMax=settings.lnbits_service_fee_max,
|
||||||
|
serviceFeeWallet=settings.lnbits_service_fee_wallet,
|
||||||
|
themeOptions=settings.lnbits_theme_options,
|
||||||
|
defaultReaction=settings.lnbits_default_reaction,
|
||||||
|
defaultTheme=settings.lnbits_default_theme,
|
||||||
|
defaultBorder=settings.lnbits_default_border,
|
||||||
|
defaultGradient=settings.lnbits_default_gradient,
|
||||||
|
defaultBgimage=settings.lnbits_default_bgimage,
|
||||||
|
defaultDark=settings.lnbits_default_dark,
|
||||||
|
defaultCardRounded=settings.lnbits_default_card_rounded,
|
||||||
|
defaultCardGradient=settings.lnbits_default_card_gradient,
|
||||||
|
defaultCardShadow=settings.lnbits_default_card_shadow,
|
||||||
|
denomination=settings.lnbits_denomination,
|
||||||
|
extensions=list(settings.lnbits_installed_extensions_ids),
|
||||||
|
allowedCurrencies=settings.lnbits_allowed_currencies,
|
||||||
|
extensionsReviewsUrl=settings.lnbits_extensions_reviews_url,
|
||||||
|
extBuilder=settings.lnbits_extensions_builder_activate_non_admins,
|
||||||
|
nostrConfigured=settings.is_nostr_notifications_configured(),
|
||||||
|
telegramConfigured=settings.is_telegram_notifications_configured(),
|
||||||
|
walletFeaturedButtonLabel=settings.lnbits_wallet_featured_button_label,
|
||||||
|
walletFeaturedButtonUrl=settings.lnbits_wallet_featured_button_url,
|
||||||
|
walletFeaturedButtonIcon=settings.lnbits_wallet_featured_button_icon,
|
||||||
|
userActivationByEmail=settings.lnbits_user_activation_by_email,
|
||||||
|
userActivationByPayment=settings.lnbits_user_activation_by_payment,
|
||||||
|
userActivationByInvitationCode=settings.lnbits_user_activation_by_invitation_code,
|
||||||
|
hasFirstInstallToken=settings.first_install_token is not None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SuperSettings(EditableSettings):
|
class SuperSettings(EditableSettings):
|
||||||
super_user: str
|
super_user: str
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+13
-11
File diff suppressed because one or more lines are too long
@@ -220,6 +220,181 @@ body.body--dark .q-drawer {
|
|||||||
backdrop-filter: blur(6px) brightness(0.8);
|
backdrop-filter: blur(6px) brightness(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.rounded-ui .q-card,
|
||||||
|
body.rounded-ui .q-btn {
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=classic].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(103, 58, 183, 0.08), rgba(156, 39, 176, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=classic].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(103, 58, 183, 0.06), rgba(156, 39, 176, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=classic].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(38.6192771084, 42.356626506, 64.7807228916), rgba(103, 58, 183, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=classic].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(36.7144578313, 40.2674698795, 61.5855421687), rgba(103, 58, 183, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=bitcoin].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(234, 97, 29, 0.08), rgba(229, 111, 53, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=bitcoin].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(234, 97, 29, 0.06), rgba(229, 111, 53, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=bitcoin].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(54.18, 49.364, 71.036), rgba(234, 97, 29, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=bitcoin].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(51.885, 47.273, 68.027), rgba(234, 97, 29, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=freedom].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(226, 33, 86, 0.08), rgba(185, 26, 69, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=freedom].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(226, 33, 86, 0.06), rgba(185, 26, 69, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=freedom].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(82.2051282051, 55.1948717949, 63.4153846154), rgba(226, 33, 86, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=freedom].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(79.1538461538, 53.1461538462, 61.0615384615), rgba(226, 33, 86, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=cyber].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(124, 179, 66, 0.08), rgba(85, 139, 47, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=cyber].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(124, 179, 66, 0.06), rgba(85, 139, 47, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=cyber].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(10.2, 10.2, 10.2), rgba(124, 179, 66, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=cyber].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(7.65, 7.65, 7.65), rgba(124, 179, 66, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=mint].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(58, 183, 125, 0.08), rgba(39, 176, 101, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=mint].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(58, 183, 125, 0.06), rgba(39, 176, 101, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=mint].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(38.6192771084, 64.7807228916, 53.5686746988), rgba(58, 183, 125, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=mint].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(36.7144578313, 61.5855421687, 50.9265060241), rgba(58, 183, 125, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=autumn].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(183, 118, 58, 0.08), rgba(176, 121, 39, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=autumn].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(183, 118, 58, 0.06), rgba(176, 121, 39, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=autumn].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(64.7807228916, 51.0771084337, 38.6192771084), rgba(183, 118, 58, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=autumn].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(61.5855421687, 48.5578313253, 36.7144578313), rgba(183, 118, 58, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=flamingo].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(255, 0, 255, 0.08), rgba(253, 163, 253, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=flamingo].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(255, 0, 255, 0.06), rgba(253, 163, 253, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=flamingo].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(66.176, 4.224, 66.176), rgba(255, 0, 255, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=flamingo].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(61.382, 3.918, 61.382), rgba(255, 0, 255, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=monochrome].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(73, 73, 73, 0.08), rgba(107, 107, 107, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=monochrome].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(73, 73, 73, 0.06), rgba(107, 107, 107, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=monochrome].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(10.2, 10.2, 10.2), rgba(73, 73, 73, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=monochrome].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(7.65, 7.65, 7.65), rgba(73, 73, 73, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=salvador].card-gradient .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgba(25, 118, 210, 0.08), rgba(38, 166, 154, 0.06));
|
||||||
|
}
|
||||||
|
body[data-theme=salvador].card-gradient .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgba(25, 118, 210, 0.06), rgba(38, 166, 154, 0.04));
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme=salvador].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark) {
|
||||||
|
background-image: linear-gradient(135deg, rgb(43.9888888889, 64.2, 84.4111111111), rgba(25, 118, 210, 0.07));
|
||||||
|
}
|
||||||
|
body[data-theme=salvador].card-gradient.body--dark .q-drawer {
|
||||||
|
background-image: linear-gradient(165deg, rgb(42.2416666667, 61.65, 81.0583333333), rgba(25, 118, 210, 0.05));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.card-shadow .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||||
|
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.18));
|
||||||
|
}
|
||||||
|
|
||||||
|
body.card-shadow.body--dark .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||||
|
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--size: 100px;
|
--size: 100px;
|
||||||
--gap: 25px;
|
--gap: 25px;
|
||||||
|
|||||||
+394
-15
@@ -6,6 +6,7 @@ window.localisation.br = {
|
|||||||
funding: 'Financiamento',
|
funding: 'Financiamento',
|
||||||
users: 'Usuários',
|
users: 'Usuários',
|
||||||
audit: 'Auditoria',
|
audit: 'Auditoria',
|
||||||
|
api_watch: 'Relógio da API',
|
||||||
apps: 'Aplicativos',
|
apps: 'Aplicativos',
|
||||||
channels: 'Canais',
|
channels: 'Canais',
|
||||||
transactions: 'Transações',
|
transactions: 'Transações',
|
||||||
@@ -21,13 +22,17 @@ window.localisation.br = {
|
|||||||
active_channels: 'Canais Ativos',
|
active_channels: 'Canais Ativos',
|
||||||
connect_peer: 'Conectar Par',
|
connect_peer: 'Conectar Par',
|
||||||
connect: 'Conectar',
|
connect: 'Conectar',
|
||||||
|
reconnect: 'Reconectar',
|
||||||
open_channel: 'Canal Aberto',
|
open_channel: 'Canal Aberto',
|
||||||
open: 'Abrir',
|
open: 'Abrir',
|
||||||
|
clear: 'Limpar',
|
||||||
close_channel: 'Fechar Canal',
|
close_channel: 'Fechar Canal',
|
||||||
close: 'Fechar',
|
close: 'Fechar',
|
||||||
restart: 'Reiniciar servidor',
|
restart: 'Reiniciar servidor',
|
||||||
|
image_library: 'Biblioteca de Imagens',
|
||||||
save: 'Salvar',
|
save: 'Salvar',
|
||||||
save_tooltip: 'Salvar suas alterações',
|
save_tooltip: 'Salvar suas alterações',
|
||||||
|
must_save: 'Você tem alterações não salvas',
|
||||||
credit_debit: 'Crédito / Débito',
|
credit_debit: 'Crédito / Débito',
|
||||||
credit_hint: 'Pressione Enter para creditar a conta',
|
credit_hint: 'Pressione Enter para creditar a conta',
|
||||||
credit_label: '{denomination} para creditar',
|
credit_label: '{denomination} para creditar',
|
||||||
@@ -46,24 +51,47 @@ window.localisation.br = {
|
|||||||
export_to_phone: 'Exportar para o telefone com código QR',
|
export_to_phone: 'Exportar para o telefone com código QR',
|
||||||
export_to_phone_desc:
|
export_to_phone_desc:
|
||||||
'Este código QR contém a URL da sua carteira com acesso total. Você pode escaneá-lo do seu telefone para abrir sua carteira a partir dele.',
|
'Este código QR contém a URL da sua carteira com acesso total. Você pode escaneá-lo do seu telefone para abrir sua carteira a partir dele.',
|
||||||
|
access_wallet_on_mobile: 'Acesso Móvel',
|
||||||
|
stored_paylinks: 'Links LNURL de pagamento armazenados',
|
||||||
wallet: 'Carteira:',
|
wallet: 'Carteira:',
|
||||||
|
wallet_name: 'Nome da carteira',
|
||||||
|
wallet_type: 'Tipo de carteira',
|
||||||
|
shared_wallet: 'Carteira Compartilhada',
|
||||||
|
share_wallet: 'Compartilhar Carteira',
|
||||||
|
update_permissions: 'Atualizar Permissões',
|
||||||
|
shared_wallet_id: 'ID da Carteira Compartilhada',
|
||||||
|
shared_wallet_desc:
|
||||||
|
'Você foi convidado(a) para ter acesso à carteira de outra pessoa.',
|
||||||
wallets: 'Carteiras',
|
wallets: 'Carteiras',
|
||||||
|
exclude_wallets: 'Excluir Carteiras',
|
||||||
add_wallet: 'Adicionar nova carteira',
|
add_wallet: 'Adicionar nova carteira',
|
||||||
|
reject_wallet: 'Rejeitar carteira',
|
||||||
|
add_new_wallet: 'Adicionar uma nova carteira',
|
||||||
|
pin_wallet: 'Fixar carteira',
|
||||||
delete_wallet: 'Excluir carteira',
|
delete_wallet: 'Excluir carteira',
|
||||||
delete_wallet_desc:
|
delete_wallet_desc:
|
||||||
'Toda a carteira será excluída, os fundos serão IRRECUPERÁVEIS.',
|
'Toda a carteira será excluída, os fundos serão IRRECUPERÁVEIS.',
|
||||||
rename_wallet: 'Renomear carteira',
|
rename_wallet: 'Renomear carteira',
|
||||||
update_name: 'Atualizar nome',
|
update_name: 'Atualizar nome',
|
||||||
fiat_tracking: 'Rastreamento Fiat',
|
fiat_tracking: 'Rastreamento Fiat',
|
||||||
|
fiat_providers: 'Provedores fiat',
|
||||||
|
fiat_warning_bitcoin:
|
||||||
|
'Provedores Fiat podem ficar nervosos com qualquer coisa relacionada ao bitcoin, portanto, evite usar a palavra "bitcoin" nos seus memorandos!',
|
||||||
currency: 'Moeda',
|
currency: 'Moeda',
|
||||||
update_currency: 'Atualizar moeda',
|
update_currency: 'Atualizar moeda',
|
||||||
press_to_claim: 'Pressione para solicitar bitcoin',
|
press_to_claim: 'Pressione para solicitar bitcoin',
|
||||||
|
claim_desc:
|
||||||
|
'Parece que você tem um valor resgatável de bitcoin, mas ainda não tem uma carteira. Pressione o botão abaixo para reivindicá-lo. Isso criará uma nova carteira para você.',
|
||||||
donate: 'Doar',
|
donate: 'Doar',
|
||||||
view_github: 'Ver no GitHub',
|
view_github: 'Ver no GitHub',
|
||||||
voidwallet_active: 'VoidWallet está ativo! Pagamentos desabilitados',
|
voidwallet_active: 'VoidWallet está ativo! Pagamentos desabilitados',
|
||||||
use_with_caution: 'USE COM CAUTELA - a carteira {name} ainda está em BETA',
|
voidwallet_active_user:
|
||||||
service_fee: 'Taxa de serviço: {amount} % por transação',
|
'Fonte de financiamento indisponível. Por favor, entre em contato com seu administrador para configurar.',
|
||||||
service_fee_max: 'Taxa de serviço: {amount} % por transação (máx {max} sats)',
|
voidwallet_active_admin:
|
||||||
|
'Fonte de financiamento indisponível. Clique aqui para configurar.',
|
||||||
|
service_fee_badge: 'Taxa de serviço: {amount} % por transação',
|
||||||
|
service_fee_max_badge:
|
||||||
|
'Taxa de serviço: {amount} % por transação (máximo {max} {denom})',
|
||||||
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',
|
||||||
@@ -79,6 +107,7 @@ window.localisation.br = {
|
|||||||
create_invoice: 'Criar Fatura',
|
create_invoice: 'Criar Fatura',
|
||||||
camera_tooltip: 'Usar a câmara para escanear uma fatura / QR',
|
camera_tooltip: 'Usar a câmara para escanear uma fatura / QR',
|
||||||
export_csv: 'Exportar para CSV',
|
export_csv: 'Exportar para CSV',
|
||||||
|
export_csv_details: 'Exportar para CSV com detalhes',
|
||||||
chart_tooltip: 'Mostrar gráfico',
|
chart_tooltip: 'Mostrar gráfico',
|
||||||
pending: 'Pendente',
|
pending: 'Pendente',
|
||||||
copy_invoice: 'Copiar fatura',
|
copy_invoice: 'Copiar fatura',
|
||||||
@@ -86,15 +115,27 @@ window.localisation.br = {
|
|||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
scan: 'Escanear',
|
scan: 'Escanear',
|
||||||
read: 'Ler',
|
read: 'Ler',
|
||||||
|
write: 'Escrever',
|
||||||
pay: 'Pagar',
|
pay: 'Pagar',
|
||||||
memo: 'Memo',
|
memo: 'Memo',
|
||||||
date: 'Data',
|
date: 'Data',
|
||||||
|
path: 'Caminho',
|
||||||
|
internal_memo: 'Memorando interno (opcional)',
|
||||||
|
internal_memo_hint_receive:
|
||||||
|
'Este memorando não é mostrado ao pagador, mas é armazenado na fatura para sua referência.',
|
||||||
|
internal_memo_hint_pay:
|
||||||
|
'Este memorando não é exibido ao beneficiário, mas é armazenado no pagamento para sua referência.',
|
||||||
payment_processing: 'Processando pagamento...',
|
payment_processing: 'Processando pagamento...',
|
||||||
|
payment_successful: 'Pagamento bem-sucedido!',
|
||||||
|
payment_pending: 'Pagamento pendente...',
|
||||||
|
payment_check: 'Cheque pagamento',
|
||||||
not_enough_funds: 'Fundos insuficientes!',
|
not_enough_funds: 'Fundos insuficientes!',
|
||||||
search_by_tag_memo_amount: 'Pesquisar por tag, memo, quantidade',
|
search_by_tag_memo_amount: 'Pesquisar por tag, memo, quantidade',
|
||||||
|
search: 'Buscar',
|
||||||
invoice_waiting: 'Fatura aguardando pagamento',
|
invoice_waiting: 'Fatura aguardando pagamento',
|
||||||
payment_received: 'Pagamento Recebido',
|
payment_received: 'Pagamento Recebido',
|
||||||
payment_sent: 'Pagamento Enviado',
|
payment_sent: 'Pagamento Enviado',
|
||||||
|
payment_failed: 'Pagamento Falhou',
|
||||||
receive: 'receber',
|
receive: 'receber',
|
||||||
send: 'enviar',
|
send: 'enviar',
|
||||||
outgoing_payment_pending: 'Pagamento pendente de saída',
|
outgoing_payment_pending: 'Pagamento pendente de saída',
|
||||||
@@ -112,7 +153,10 @@ window.localisation.br = {
|
|||||||
extensions: 'Extensões',
|
extensions: 'Extensões',
|
||||||
no_extensions: 'Você não possui nenhuma extensão instalada :(',
|
no_extensions: 'Você não possui nenhuma extensão instalada :(',
|
||||||
created: 'Criado',
|
created: 'Criado',
|
||||||
|
created_at: 'Criado em',
|
||||||
|
updated_at: 'Atualizado em',
|
||||||
search_extensions: 'Extensões de pesquisa',
|
search_extensions: 'Extensões de pesquisa',
|
||||||
|
search_wallets: 'Pesquisar carteiras',
|
||||||
extension_sources: 'Fontes de Extensão',
|
extension_sources: 'Fontes de Extensão',
|
||||||
ext_sources_hint: 'Repositórios de onde as extensões podem ser baixadas',
|
ext_sources_hint: 'Repositórios de onde as extensões podem ser baixadas',
|
||||||
ext_sources_label:
|
ext_sources_label:
|
||||||
@@ -121,10 +165,13 @@ window.localisation.br = {
|
|||||||
repository: 'Repositório',
|
repository: 'Repositório',
|
||||||
confirm_continue: 'Você tem certeza de que deseja continuar?',
|
confirm_continue: 'Você tem certeza de que deseja continuar?',
|
||||||
manage_extension_details: 'Instalar/desinstalar extensão',
|
manage_extension_details: 'Instalar/desinstalar extensão',
|
||||||
|
upload: 'Enviar',
|
||||||
install: 'Instalar',
|
install: 'Instalar',
|
||||||
uninstall: 'Desinstalar',
|
uninstall: 'Desinstalar',
|
||||||
drop_db: 'Remover Dados',
|
drop_db: 'Remover Dados',
|
||||||
enable: 'Ativar',
|
enable: 'Ativar',
|
||||||
|
enabled: 'Habilitado',
|
||||||
|
disabled: 'Desativado',
|
||||||
pay_to_enable: 'Pague para Habilitar',
|
pay_to_enable: 'Pague para Habilitar',
|
||||||
enable_extension_details: 'Ativar extensão para o usuário atual',
|
enable_extension_details: 'Ativar extensão para o usuário atual',
|
||||||
disable: 'Desativar',
|
disable: 'Desativar',
|
||||||
@@ -132,6 +179,8 @@ window.localisation.br = {
|
|||||||
installed: 'Instalado',
|
installed: 'Instalado',
|
||||||
activated: 'Ativado',
|
activated: 'Ativado',
|
||||||
deactivated: 'Desativado',
|
deactivated: 'Desativado',
|
||||||
|
activate: 'Ativar',
|
||||||
|
deactivate: 'Desativar',
|
||||||
release_notes: 'Notas de Lançamento',
|
release_notes: 'Notas de Lançamento',
|
||||||
activate_extension_details:
|
activate_extension_details:
|
||||||
'Tornar a extensão disponível/indisponível para usuários',
|
'Tornar a extensão disponível/indisponível para usuários',
|
||||||
@@ -139,8 +188,33 @@ window.localisation.br = {
|
|||||||
all: 'Tudo',
|
all: 'Tudo',
|
||||||
only_admins_can_install:
|
only_admins_can_install:
|
||||||
'Apenas contas de administrador podem instalar extensões.',
|
'Apenas contas de administrador podem instalar extensões.',
|
||||||
|
only_admins_can_create_extensions:
|
||||||
|
'Apenas contas de administrador podem criar extensões',
|
||||||
admin_only: 'Apenas para Administração',
|
admin_only: 'Apenas para Administração',
|
||||||
|
make_user_admin: 'Tornar usuário administrador',
|
||||||
|
revoke_admin: 'Revogar Admin',
|
||||||
new_version: 'Nova Versão',
|
new_version: 'Nova Versão',
|
||||||
|
reviews_url: 'URL de Avaliações',
|
||||||
|
reviews_url_label: 'URL do servidor de avaliações',
|
||||||
|
reviews_url_hint:
|
||||||
|
'URL completa do PaidReviews incluindo o id das configurações (por exemplo, https://example.com/paidreviews/SETTINGS_ID)',
|
||||||
|
reviews_open: 'Ver avaliações',
|
||||||
|
reviews_leave: 'Deixe uma avaliação',
|
||||||
|
reviews_name: 'Seu nome',
|
||||||
|
reviews_comment: 'Sua avaliação',
|
||||||
|
reviews_rating: 'Avaliação',
|
||||||
|
reviews_submit: 'Enviar avaliação',
|
||||||
|
reviews_loading: 'Carregando avaliações...',
|
||||||
|
reviews_refresh: 'Atualizar avaliações',
|
||||||
|
reviews_error_load: 'Não foi possível carregar as avaliações',
|
||||||
|
reviews_url_not_configured: 'URL de avaliações não configurada',
|
||||||
|
reviews_pay_invoice: 'Pagar fatura',
|
||||||
|
reviews_invoice_paid: 'Fatura paga',
|
||||||
|
reviews_invoice_title: 'Pague esta fatura para enviar sua avaliação',
|
||||||
|
reviews_count: 'Avaliações',
|
||||||
|
no_reviews: 'Ainda não há avaliações',
|
||||||
|
extension_has_free_release: 'Tem lançamentos gratuitos',
|
||||||
|
extension_has_paid_release: 'Tem lançamentos pagos',
|
||||||
extension_depends_on: 'Depende de:',
|
extension_depends_on: 'Depende de:',
|
||||||
extension_rating_soon: 'Avaliações estarão disponíveis em breve',
|
extension_rating_soon: 'Avaliações estarão disponíveis em breve',
|
||||||
extension_installed_version: 'Versão instalada',
|
extension_installed_version: 'Versão instalada',
|
||||||
@@ -155,26 +229,122 @@ window.localisation.br = {
|
|||||||
'Esta versão requer no mínimo a versão do LNbits',
|
'Esta versão requer no mínimo a versão do LNbits',
|
||||||
min_version: 'Mínimo (incluído)',
|
min_version: 'Mínimo (incluído)',
|
||||||
max_version: 'Máximo (excluído)',
|
max_version: 'Máximo (excluído)',
|
||||||
|
preimage: 'Pré-imagem',
|
||||||
|
preimage_hint: 'Pré-imagem para liquidar a fatura de retenção',
|
||||||
|
hold_invoice: 'Reter Fatura',
|
||||||
|
hold_invoice_description:
|
||||||
|
'Esta fatura está em espera e requer uma pré-imagem para ser liquidada.',
|
||||||
payment_hash: 'Hash de pagamento',
|
payment_hash: 'Hash de pagamento',
|
||||||
|
invoice_cancelled: 'Fatura Cancelada',
|
||||||
|
invoice_settled: 'Fatura Liquidada',
|
||||||
|
hold_invoice_payment_hash:
|
||||||
|
'Hash de pagamento para fatura em espera (opcional)',
|
||||||
|
settle_invoice: 'Liquidar Fatura',
|
||||||
|
cancel_invoice: 'Cancelar Fatura',
|
||||||
fee: 'Taxa',
|
fee: 'Taxa',
|
||||||
amount: 'Quantidade',
|
amount: 'Quantidade',
|
||||||
|
amount_limits: 'Limites de Quantia',
|
||||||
amount_sats: 'Quantidade (sats)',
|
amount_sats: 'Quantidade (sats)',
|
||||||
|
faucest_wallet: 'Carteira de Torneira',
|
||||||
|
faucest_wallet_desc_1:
|
||||||
|
'Toda vez que um pagamento for confirmado pelo provedor {provider}, os fundos serão subtraídos desta carteira.',
|
||||||
|
faucest_wallet_desc_2:
|
||||||
|
'Isso ajuda a monitorar todos os pagamentos do {provider} e seu status.',
|
||||||
|
faucest_wallet_desc_3:
|
||||||
|
'Esta carteira deve ser recarregada com a quantia de sats que o administrador está disposto a oferecer em troca da moeda fiduciária.',
|
||||||
|
faucest_wallet_desc_4:
|
||||||
|
'Se esta carteira estiver configurada, mas estiver vazia, os pagamentos de {provider} não serão processados.',
|
||||||
|
faucest_wallet_desc_5:
|
||||||
|
'Esta carteira pode eventualmente ficar com saldo negativo se pagamentos fiduciários paralelos forem feitos.',
|
||||||
|
faucest_wallet_id: 'ID da Carteira de Torneira (opcional)',
|
||||||
|
faucest_wallet_id_hint:
|
||||||
|
'ID da carteira a ser usado para a torneira. Será usado para enviar os fundos ao usuário.',
|
||||||
tag: 'Etiqueta',
|
tag: 'Etiqueta',
|
||||||
unit: 'Unidade',
|
unit: 'Unidade',
|
||||||
description: 'Descrição',
|
description: 'Descrição',
|
||||||
expiry: 'Validade',
|
expiry: 'Validade',
|
||||||
webhook: 'Webhook',
|
webhook: 'Webhook',
|
||||||
|
webhook_url: 'URL do Webhook',
|
||||||
|
webhook_url_hint:
|
||||||
|
'URL de Webhook para enviar os detalhes do pagamento. Será chamada quando o pagamento for concluído.',
|
||||||
|
copy_webhook_url: 'Copiar URL do webhook',
|
||||||
|
webhook_events_list:
|
||||||
|
'Os seguintes eventos devem ser suportados pelo webhook:',
|
||||||
|
webhook_stripe_description:
|
||||||
|
'No lado do Stripe, você deve configurar um webhook com um URL que aponta para o seu servidor LNbits.',
|
||||||
payment_proof: 'Comprovante de pagamento',
|
payment_proof: 'Comprovante de pagamento',
|
||||||
update: 'Atualizar',
|
update: 'Atualizar',
|
||||||
update_available: 'Atualização {version} disponível!',
|
update_available: 'Atualização {version} disponível!',
|
||||||
|
funding_sources: 'Fontes de Financiamento',
|
||||||
latest_update: 'Você está na versão mais recente {version}.',
|
latest_update: 'Você está na versão mais recente {version}.',
|
||||||
notifications: 'Notificações',
|
notifications: 'Notificações',
|
||||||
no_notifications: 'Sem notificações',
|
notifications_configure: 'Configurar Notificações',
|
||||||
notifications_disabled:
|
notifications_nostr_config: 'Configuração do Nostr',
|
||||||
'As notificações de status do LNbits estão desativadas.',
|
notifications_enable_nostr: 'Ativar Nostr',
|
||||||
enable_notifications: 'Ativar notificações',
|
notifications_enable_nostr_desc: 'Enviar notificações pelo Nostr',
|
||||||
enable_notifications_desc:
|
notifications_nostr_private_key: 'Chave Privada Nostr',
|
||||||
'Se ativado, ele buscará as últimas atualizações de status do LNbits, como incidentes de segurança e atualizações.',
|
notifications_nostr_private_key_desc:
|
||||||
|
'Chave privada (hex ou nsec) para assinar as mensagens enviadas para Nostr',
|
||||||
|
notifications_nostr_identifier: 'Identificador Nostr',
|
||||||
|
notifications_nostr_identifier_desc:
|
||||||
|
'Identificador Nip5 para enviar notificações para',
|
||||||
|
notifications_nostr_identifiers: 'Identificadores Nostr',
|
||||||
|
notifications_nostr_identifiers_desc:
|
||||||
|
'Lista de identificadores para enviar notificações.',
|
||||||
|
notifications_telegram_config: 'Configuração do Telegram',
|
||||||
|
notifications_enable_telegram: 'Ativar Telegram',
|
||||||
|
notifications_enable_telegram_desc: 'Enviar notificações pelo Telegram',
|
||||||
|
notifications_telegram_access_token: 'Token de Acesso',
|
||||||
|
notifications_telegram_access_token_desc: 'Token de acesso para o bot',
|
||||||
|
notifications_chat_id: 'ID de bate-papo do Telegram',
|
||||||
|
notifications_chat_id_desc:
|
||||||
|
'ID do chat do Telegram para enviar as notificações para',
|
||||||
|
notifications_excluded_wallets_desc:
|
||||||
|
'Não envie notificações para essas carteiras',
|
||||||
|
notifications_email_config: 'Configuração de Email',
|
||||||
|
notifications_enable_email: 'Habilitar Email',
|
||||||
|
notifications_enable_email_desc: 'Enviar notificações por e-mail',
|
||||||
|
notifications_send_test_email: 'Enviar e-mail de teste',
|
||||||
|
notifications_send_email: 'Enviar e-mail',
|
||||||
|
notifications_send_email_desc: 'Email que você enviará de',
|
||||||
|
notifications_send_email_username: 'Nome de usuário',
|
||||||
|
notifications_send_email_username_desc:
|
||||||
|
'Nome de usuário, usará o e-mail se não estiver definido',
|
||||||
|
notifications_send_email_password: 'Enviar senha de e-mail',
|
||||||
|
notifications_send_email_password_desc:
|
||||||
|
'Senha para o e-mail que você enviará de',
|
||||||
|
notifications_send_email_server_port: 'Enviar e-mail porta SMTP',
|
||||||
|
notifications_send_email_server_port_desc: 'Porta para o servidor SMTP',
|
||||||
|
notifications_send_email_server: 'Enviar e-mail servidor SMTP',
|
||||||
|
notifications_send_email_server_desc:
|
||||||
|
'Servidor SMTP para o e-mail de que você enviará',
|
||||||
|
notifications_send_to_emails: 'Emails para enviar para',
|
||||||
|
notifications_send_to_emails_desc:
|
||||||
|
'Notificações de e-mails serão enviadas para',
|
||||||
|
notification_settings_update: 'Configurações atualizadas',
|
||||||
|
notification_settings_update_desc:
|
||||||
|
'Notificar quando as configurações do servidor forem atualizadas',
|
||||||
|
notification_server_start_stop: 'Iniciar/Parar Servidor',
|
||||||
|
notification_server_start_stop_desc:
|
||||||
|
'Notificar quando o servidor tiver sido iniciado/parado',
|
||||||
|
notification_watchdog_limit: 'Notificação de Limite do Watchdog',
|
||||||
|
notification_watchdog_limit_desc:
|
||||||
|
'Notifique quando o limite do watchdog for alcançado (não afeta a fonte de financiamento)',
|
||||||
|
notification_server_status: 'Status do Servidor',
|
||||||
|
notification_server_status_desc:
|
||||||
|
'Enviar notificações regulares sobre o status do servidor (valor do intervalo em horas)',
|
||||||
|
notification_incoming_payment: 'Pagamentos Recebidos',
|
||||||
|
notification_incoming_payment_desc:
|
||||||
|
'Notificar quando uma carteira tiver recebido um pagamento acima do valor especificado (sats)',
|
||||||
|
notification_outgoing_payment: 'Pagamentos Saída',
|
||||||
|
notification_outgoing_payment_desc:
|
||||||
|
'Notificar quando uma carteira tiver enviado um pagamento acima do valor especificado (sats)',
|
||||||
|
notification_credit_debit: 'Crédito / Débito',
|
||||||
|
notification_credit_debit_desc:
|
||||||
|
'Notificar quando uma carteira tiver sido creditada/debitada pelo superusuário',
|
||||||
|
notification_balance_delta_changed: 'Mudança no Delta de Saldo',
|
||||||
|
notification_balance_delta_changed_desc:
|
||||||
|
'Notifique quando a diferença entre o saldo do nó e o saldo do LNbits tiver mudado mais do que a quantidade especificada (em sats). Defina como 0 para desativar. Isso é executado a cada minuto.',
|
||||||
enable_watchdog: 'Ativar Watchdog',
|
enable_watchdog: 'Ativar Watchdog',
|
||||||
enable_watchdog_desc:
|
enable_watchdog_desc:
|
||||||
'Se ativado, ele mudará automaticamente sua fonte de financiamento para VoidWallet se o seu saldo for inferior ao saldo do LNbits. Você precisará ativar manualmente após uma atualização.',
|
'Se ativado, ele mudará automaticamente sua fonte de financiamento para VoidWallet se o seu saldo for inferior ao saldo do LNbits. Você precisará ativar manualmente após uma atualização.',
|
||||||
@@ -189,6 +359,7 @@ window.localisation.br = {
|
|||||||
notification_source_label:
|
notification_source_label:
|
||||||
'URL de origem (use apenas a fonte de status oficial do LNbits e fontes de confiança)',
|
'URL de origem (use apenas a fonte de status oficial do LNbits e fontes de confiança)',
|
||||||
more: 'mais',
|
more: 'mais',
|
||||||
|
more_count: 'Mais {count}',
|
||||||
less: 'menos',
|
less: 'menos',
|
||||||
releases: 'Lançamentos',
|
releases: 'Lançamentos',
|
||||||
watchdog: 'Cão de guarda',
|
watchdog: 'Cão de guarda',
|
||||||
@@ -200,15 +371,24 @@ 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',
|
||||||
|
callback_url_rules: 'Regras de URL de Retorno',
|
||||||
|
enter_callback_url_rule: 'Digite a regra de URL como regex e pressione enter',
|
||||||
|
callback_url_rule_hint:
|
||||||
|
'URLs de retorno de chamada (como a de LNURL) serão validados conforme estas regras. Pelo menos uma regra deve corresponder. Nenhuma regra significa que todas as URLs são permitidas.',
|
||||||
wallet_limiter: 'Limitador de Carteira',
|
wallet_limiter: 'Limitador de Carteira',
|
||||||
|
wallet_config: 'Configuração da Carteira',
|
||||||
|
wallet_charts: 'Gráficos da Carteira',
|
||||||
wallet_limit_max_withdraw_per_day:
|
wallet_limit_max_withdraw_per_day:
|
||||||
'Retirada máxima diária da carteira em sats (0 para desativar)',
|
'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_max_ballance: 'Saldo máximo da carteira em sats (0 para desativar)',
|
||||||
wallet_limit_secs_between_trans:
|
wallet_limit_secs_between_trans:
|
||||||
'Minutos e segundos entre transações por carteira (0 para desativar)',
|
'Minutos e segundos entre transações por carteira (0 para desativar)',
|
||||||
|
only_incoming_payments_allowed: 'Apenas pagamentos recebidos são permitidos',
|
||||||
|
disable_outgoing_payments: 'Desativar pagamentos de saída',
|
||||||
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',
|
||||||
|
settings: 'Configurações',
|
||||||
second: 'segundo',
|
second: 'segundo',
|
||||||
hour: 'hora',
|
hour: 'hora',
|
||||||
disable_server_log: 'Desativar Log do Servidor',
|
disable_server_log: 'Desativar Log do Servidor',
|
||||||
@@ -219,49 +399,108 @@ window.localisation.br = {
|
|||||||
login_with_user_id: 'Faça login com ID do usuário',
|
login_with_user_id: 'Faça login com ID do usuário',
|
||||||
or: 'ou',
|
or: 'ou',
|
||||||
create_new_wallet: 'Criar Nova Carteira',
|
create_new_wallet: 'Criar Nova Carteira',
|
||||||
|
delete_all_wallets: 'Excluir Todas as Carteiras',
|
||||||
|
confirm_delete_all_wallets:
|
||||||
|
'Tem certeza de que deseja excluir TODAS as carteiras deste usuário?',
|
||||||
login_to_account: 'Faça login na sua conta',
|
login_to_account: 'Faça login na sua conta',
|
||||||
create_account: 'Criar conta',
|
create_account: 'Criar conta',
|
||||||
account_settings: 'Configurações da Conta',
|
account_settings: 'Configurações da Conta',
|
||||||
|
signin_with_oauth: 'Entrar com',
|
||||||
|
signin_with_oauth_or: 'ou entre com',
|
||||||
signin_with_nostr: 'Continuar com Nostr',
|
signin_with_nostr: 'Continuar com Nostr',
|
||||||
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',
|
signin_with_custom_org: 'Entrar com {custom_org}',
|
||||||
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',
|
||||||
password_repeat: 'Repetição de senha',
|
password_repeat: 'Repetição de senha',
|
||||||
|
update_password: 'Atualizar Senha',
|
||||||
change_password: 'Alterar Senha',
|
change_password: 'Alterar Senha',
|
||||||
update_credentials: 'Atualizar credenciais',
|
update_credentials: 'Atualizar credenciais',
|
||||||
update_pubkey: 'Atualizar Chave Pública',
|
update_pubkey: 'Atualizar Chave Pública',
|
||||||
|
nostr_pubkey_tooltip:
|
||||||
|
'Insira a chave pública Nostr deste usuário (valor hexadecimal)',
|
||||||
set_password: 'Definir Senha',
|
set_password: 'Definir Senha',
|
||||||
|
set_password_tooltip: 'Defina uma senha para este usuário',
|
||||||
invalid_password: 'A senha deve ter pelo menos 8 caracteres',
|
invalid_password: 'A senha deve ter pelo menos 8 caracteres',
|
||||||
|
invalid_password_repeat: 'As senhas não coincidem',
|
||||||
|
reset_key_generated: 'Uma chave de reinicialização foi gerada.',
|
||||||
|
reset_key_copy:
|
||||||
|
'Clique em OK para copiar o URL de redefinição para sua área de transferência.',
|
||||||
login: 'Entrar',
|
login: 'Entrar',
|
||||||
register: 'Registrar',
|
register: 'Registrar',
|
||||||
username: 'Nome de usuário',
|
username: 'Nome de usuário',
|
||||||
pubkey: 'Chave Pública',
|
pubkey: 'Chave Pública',
|
||||||
user_id: 'ID do Usuário',
|
user_id: 'ID do Usuário',
|
||||||
|
id: 'ID',
|
||||||
email: 'E-mail',
|
email: 'E-mail',
|
||||||
first_name: 'Primeiro Nome',
|
first_name: 'Primeiro Nome',
|
||||||
last_name: 'Sobrenome',
|
last_name: 'Sobrenome',
|
||||||
picture: 'Foto',
|
picture: 'Foto',
|
||||||
|
user_picture_desc:
|
||||||
|
'URL para uma imagem a ser usada como foto de perfil. Você pode carregá-la como um ativo.',
|
||||||
verify_email: 'Verifique o e-mail com',
|
verify_email: 'Verifique o e-mail com',
|
||||||
account: 'Conta',
|
account: 'Conta',
|
||||||
update_account: 'Atualizar Conta',
|
update_account: 'Atualizar Conta',
|
||||||
invalid_username: 'Nome de usuário inválido',
|
invalid_username: 'Nome de usuário inválido',
|
||||||
auth_provider: 'Provedor de Autenticação',
|
auth_provider: 'Provedor de Autenticação',
|
||||||
|
external_id: 'ID Externo',
|
||||||
my_account: 'Minha Conta',
|
my_account: 'Minha Conta',
|
||||||
|
existing_account_question: 'Já tem uma conta?',
|
||||||
|
background_image: 'Imagem de Fundo',
|
||||||
back: 'Voltar',
|
back: 'Voltar',
|
||||||
logout: 'Sair',
|
logout: 'Sair',
|
||||||
look_and_feel: 'Aparência',
|
look_and_feel: 'Aparência',
|
||||||
|
endpoint: 'Ponto de extremidade',
|
||||||
|
api: 'API',
|
||||||
|
api_stripe: 'API',
|
||||||
|
api_token: 'Token de API',
|
||||||
|
api_tokens: 'Tokens da API',
|
||||||
|
access_control_list: 'Lista de Controle de Acesso',
|
||||||
|
access_control_list_admin_warning:
|
||||||
|
'Esta é uma conta de administrador. Os tokens gerados terão privilégios de administrador.',
|
||||||
|
new_api_acl: 'Nova Lista de Controle de Acesso',
|
||||||
|
api_token_id: 'Id do Token',
|
||||||
toggle_gradient: 'Alternar Gradiente',
|
toggle_gradient: 'Alternar Gradiente',
|
||||||
gradient_background: 'Fundo em Degradê',
|
gradient_background: 'Fundo em Degradê',
|
||||||
|
rounded_ui: 'Cartões e Botões Arredondados',
|
||||||
|
toggle_rounded_ui: 'Alternar cantos arredondados para cartões e botões',
|
||||||
|
card_gradient: 'Gradiente do Cartão',
|
||||||
|
toggle_card_gradient: 'Alternar gradiente nos cartões',
|
||||||
|
card_shadow: 'Sombra do Cartão',
|
||||||
|
toggle_card_shadow: 'Alternar sombra nas cartas',
|
||||||
language: 'Idioma',
|
language: 'Idioma',
|
||||||
|
assets: 'Ativos',
|
||||||
|
max_asset_size_mb: 'Tamanho Máximo do Ativo (MB)',
|
||||||
|
max_asset_size_mb_desc:
|
||||||
|
'O tamanho máximo permitido para uploads de ativos em megabytes (pode usar valores decimais).',
|
||||||
|
assets_allowed_mime_types: 'Tipos MIME permitidos',
|
||||||
|
assets_allowed_mime_types_desc:
|
||||||
|
'Os tipos MIME permitidos para uploads de ativos. Nenhum valor significa que todos os uploads são permitidos.',
|
||||||
|
thumbnail_width: 'Largura da Miniatura',
|
||||||
|
thumbnail_width_desc: 'Largura da miniatura gerada em pixels.',
|
||||||
|
thumbnail_height: 'Altura da miniatura',
|
||||||
|
thumbnail_height_desc: 'Altura da miniatura gerada em pixels.',
|
||||||
|
thumbnail_format: 'Formato da Miniatura',
|
||||||
|
thumbnail_format_desc:
|
||||||
|
'Formato de imagem da miniatura gerada (PNG, JPEG, etc.).',
|
||||||
|
max_assets_per_user: 'Máximo de ativos por usuário',
|
||||||
|
max_assets_per_user_desc:
|
||||||
|
'O número máximo de ativos que um usuário pode fazer upload. Zero significa que o upload está proibido.',
|
||||||
|
assets_no_limit_users: 'Usuários sem Limites de Ativos',
|
||||||
|
assets_no_limit_users_desc:
|
||||||
|
'Esses usuários podem enviar um número ilimitado de ativos (com base no ID do usuário).',
|
||||||
color_scheme: 'Esquema de Cores',
|
color_scheme: 'Esquema de Cores',
|
||||||
|
visible_wallet_count: 'Contagem de Carteiras Visíveis',
|
||||||
admin_settings: 'Configurações do Administrador',
|
admin_settings: 'Configurações do Administrador',
|
||||||
extension_cost: 'Este lançamento requer um pagamento mínimo de {cost} sats.',
|
extension_cost: 'Este lançamento requer um pagamento mínimo de {cost} sats.',
|
||||||
extension_paid_sats: 'Você já pagou {paid_sats} sats.',
|
extension_paid_sats: 'Você já pagou {paid_sats} sats.',
|
||||||
|
create_extension: 'Criar Extensão',
|
||||||
release_details_error: 'Não é possível obter os detalhes da versão.',
|
release_details_error: 'Não é possível obter os detalhes da versão.',
|
||||||
pay_from_wallet: 'Pagar com a Carteira',
|
pay_from_wallet: 'Pagar com a Carteira',
|
||||||
|
pay_with: 'Pague com {provider}',
|
||||||
|
select_payment_provider: 'Selecione o provedor de pagamento',
|
||||||
wallet_required: 'Carteira *',
|
wallet_required: 'Carteira *',
|
||||||
show_qr: 'Exibir QR',
|
show_qr: 'Exibir QR',
|
||||||
retry_install: 'Repetir Instalação',
|
retry_install: 'Repetir Instalação',
|
||||||
@@ -274,6 +513,9 @@ window.localisation.br = {
|
|||||||
'A extensão {name} requer um pagamento mínimo de {amount} sats para habilitar.',
|
'A extensão {name} requer um pagamento mínimo de {amount} sats para habilitar.',
|
||||||
hide_empty_wallets: 'Ocultar carteiras vazias',
|
hide_empty_wallets: 'Ocultar carteiras vazias',
|
||||||
recheck: 'Verificar novamente',
|
recheck: 'Verificar novamente',
|
||||||
|
check: 'Verificar',
|
||||||
|
check_connection: 'Verificar Conexão',
|
||||||
|
check_webhook: 'Verificar Webhook',
|
||||||
contributors: 'Contribuidores',
|
contributors: 'Contribuidores',
|
||||||
license: 'Licença',
|
license: 'Licença',
|
||||||
reset_key: 'Redefinir Chave',
|
reset_key: 'Redefinir Chave',
|
||||||
@@ -286,6 +528,7 @@ window.localisation.br = {
|
|||||||
hash: 'Hash:',
|
hash: 'Hash:',
|
||||||
welcome_lnbits: 'Bem-vindo ao LNbits',
|
welcome_lnbits: 'Bem-vindo ao LNbits',
|
||||||
setup_su_account: 'Configure a conta Superuser abaixo.',
|
setup_su_account: 'Configure a conta Superuser abaixo.',
|
||||||
|
first_install_token: 'Primeiro Token de Instalação',
|
||||||
create_ticker_converter: 'Criar Conversor de Ticker de Moeda',
|
create_ticker_converter: 'Criar Conversor de Ticker de Moeda',
|
||||||
enable_audit: 'Habilitar Auditoria',
|
enable_audit: 'Habilitar Auditoria',
|
||||||
recommended: 'Recomendado',
|
recommended: 'Recomendado',
|
||||||
@@ -326,9 +569,16 @@ window.localisation.br = {
|
|||||||
user_default_extensions_label: 'Extensões do usuário',
|
user_default_extensions_label: 'Extensões do usuário',
|
||||||
user_default_extensions_hint:
|
user_default_extensions_hint:
|
||||||
'Extensões que serão ativadas por padrão para os usuários.',
|
'Extensões que serão ativadas por padrão para os usuários.',
|
||||||
|
extension_builder: 'Construtor de Extensão',
|
||||||
|
extension_builder_manifest_url: 'URL do Manifesto do Criador de Extensões',
|
||||||
|
extension_builder_manifest_url_hint:
|
||||||
|
'URL para um arquivo JSON manifest com detalhes do extension builder',
|
||||||
miscellanous: 'Diversos',
|
miscellanous: 'Diversos',
|
||||||
misc_disable_extensions: 'Desativar extensões',
|
misc_disable_extensions: 'Desativar extensões',
|
||||||
misc_disable_extensions_label: 'Desativar todas as extensões',
|
misc_disable_extensions_label: 'Desativar todas as extensões',
|
||||||
|
misc_disable_extensions_builder: 'Habilitar Extensions Builder',
|
||||||
|
misc_disable_extensions_builder_label:
|
||||||
|
'Habilitar Extensions Builder para usuários não administradores.',
|
||||||
misc_hide_api: 'Ocultar API',
|
misc_hide_api: 'Ocultar API',
|
||||||
misc_hide_api_label:
|
misc_hide_api_label:
|
||||||
'Oculta a API de carteira, extensões podem optar por honrar',
|
'Oculta a API de carteira, extensões podem optar por honrar',
|
||||||
@@ -346,16 +596,33 @@ window.localisation.br = {
|
|||||||
toggle_transactions_node_ui:
|
toggle_transactions_node_ui:
|
||||||
'Guia de Transações (Desativar em nós grandes CLN)',
|
'Guia de Transações (Desativar em nós grandes CLN)',
|
||||||
invoice_expiry: 'Expiração da Fatura',
|
invoice_expiry: 'Expiração da Fatura',
|
||||||
invoice_expiry_label: 'Validade da fatura (segundos)',
|
invoice_expiry_label: 'Validade da fatura',
|
||||||
fee_reserve: 'Reserva de Taxa',
|
routing_fee_reserve_calculations: 'Cálculos de Reserva de Taxa de Roteamento',
|
||||||
fee_reserve_msats: 'Taxa de reserva em msats',
|
routing_fee_reserve_calculations_desc:
|
||||||
fee_reserve_percent: 'Taxa de reserva em porcentagem',
|
'LNbits reserva um "valor de reserva" para cada pagamento para cobrir as taxas de roteamento. A taxa de roteamento máxima passada para a fonte de financiamento é a que for maior: a <strong>reserva mínima de taxa de roteamento</strong> ou a <strong>percentagem de reserva de taxa de roteamento</strong>.',
|
||||||
|
millisats: 'milissats',
|
||||||
|
fee_reserve: 'Reserva Mínima de Taxa de Encaminhamento',
|
||||||
|
fee_reserve_percent: 'Porcentagem da Reserva de Taxa de Roteamento',
|
||||||
|
fee_reserve_min_hint:
|
||||||
|
'A taxa mínima reservada por pagamento.<br>Isso atua como um piso - a taxa de roteamento máxima nunca será inferior a este valor, independentemente do tamanho do pagamento.',
|
||||||
|
fee_reserve_percent_hint:
|
||||||
|
'A porcentagem do valor do pagamento a reservar para taxas de roteamento.',
|
||||||
|
payment_timeouts: 'Tempos de Espera de Pagamento',
|
||||||
|
payment_wait_time: 'Tempo de Espera do Pagamento',
|
||||||
|
seconds: 'segundos',
|
||||||
|
payment_wait_time_desc:
|
||||||
|
'Tempo de espera antes de marcar um pagamento de saída como pendente. Padrão: 5s; aumentar para faturas de liquidação lenta.',
|
||||||
|
payment_wait_time_tooltip:
|
||||||
|
'Controla quanto tempo o LNbits espera para uma tentativa de pagamento de saída ser confirmada antes de marcá-la como pendente. Valores mais altos ajudam ao pagar faturas de liquidação lenta (por exemplo, faturas HODL, Boltz). O pagamento será verificado novamente mais tarde e atualizado automaticamente ou manualmente.',
|
||||||
server_management: 'Gerenciamento de Servidor',
|
server_management: 'Gerenciamento de Servidor',
|
||||||
base_url: 'URL base',
|
base_url: 'URL base',
|
||||||
base_url_label: 'URL estática/base para o servidor',
|
base_url_label: 'URL estática/base para o servidor',
|
||||||
authentication: 'Autenticação',
|
authentication: 'Autenticação',
|
||||||
auth_token_expiry_label: 'Minutos para expiração do token',
|
auth_token_expiry_label: 'Minutos para expiração do token',
|
||||||
auth_token_expiry_hint: 'Tempo em minutos até o token expirar',
|
auth_token_expiry_hint: 'Tempo em minutos até o token expirar',
|
||||||
|
auth_authentication_cache_label: 'Tempo de cache (minutos)',
|
||||||
|
auth_authentication_cache_hint:
|
||||||
|
'Tempo em minutos para armazenar em cache a autenticação bem-sucedida (0 para desativar)',
|
||||||
auth_allowed_methods_label: 'Métodos de autorização permitidos',
|
auth_allowed_methods_label: 'Métodos de autorização permitidos',
|
||||||
auth_allowed_methods_hint: 'Selecione métodos de autorização',
|
auth_allowed_methods_hint: 'Selecione métodos de autorização',
|
||||||
auth_nostr_label: 'URL de Solicitação Nostr',
|
auth_nostr_label: 'URL de Solicitação Nostr',
|
||||||
@@ -373,13 +640,26 @@ window.localisation.br = {
|
|||||||
auth_keycloak_ci_hint:
|
auth_keycloak_ci_hint:
|
||||||
'Certifique-se de que a URL de retorno de chamada de autorização esteja definida para https://{domain}/api/v1/auth/keycloak/token',
|
'Certifique-se de que a URL de retorno de chamada de autorização esteja definida para https://{domain}/api/v1/auth/keycloak/token',
|
||||||
auth_keycloak_cs_label: 'Segredo do Cliente Keycloak',
|
auth_keycloak_cs_label: 'Segredo do Cliente Keycloak',
|
||||||
|
auth_keycloak_custom_org_label: 'Keycloak Custom Organization',
|
||||||
|
auth_keycloak_custom_icon_label: 'Ícone Personalizado do Keycloak (URL)',
|
||||||
currency_settings: 'Configurações de Moeda',
|
currency_settings: 'Configurações de Moeda',
|
||||||
allowed_currencies: 'Moedas Permitidas',
|
allowed_currencies: 'Moedas Permitidas',
|
||||||
allowed_currencies_hint: 'Limite o número de moedas fiduciárias disponíveis',
|
allowed_currencies_hint: 'Limite o número de moedas fiduciárias disponíveis',
|
||||||
default_account_currency: 'Moeda Padrão da Conta',
|
default_account_currency: 'Moeda Padrão da Conta',
|
||||||
default_account_currency_hint: 'Moeda padrão para contabilidade',
|
default_account_currency_hint: 'Moeda padrão para contabilidade',
|
||||||
|
min_incoming_payment_amount: 'Quantia Mínima de Pagamento de Entrada',
|
||||||
|
min_incoming_payment_amount_desc:
|
||||||
|
'Quantidade mínima permitida para gerar uma fatura',
|
||||||
|
max_incoming_payment_amount: 'Valor Máximo do Pagamento Recebido',
|
||||||
|
max_incoming_payment_amount_desc:
|
||||||
|
'Quantidade máxima permitida para gerar uma fatura',
|
||||||
|
max_outgoing_payment_amount: 'Valor Máximo de Pagamento de Saída',
|
||||||
|
max_outgoing_payment_amount_desc:
|
||||||
|
'Valor máximo permitido para efetuar um pagamento',
|
||||||
|
service_fee: 'Taxa de serviço: {amount} % por transação',
|
||||||
service_fee_label: 'Taxa de serviço (%)',
|
service_fee_label: 'Taxa de serviço (%)',
|
||||||
service_fee_hint: 'Taxa cobrada por tx (%)',
|
service_fee_hint: 'Taxa cobrada por tx (%)',
|
||||||
|
service_fee_max: 'Taxa de serviço: {amount} % por transação (máx {max} sats)',
|
||||||
service_fee_max_label: 'Taxa de serviço máx (sats)',
|
service_fee_max_label: 'Taxa de serviço máx (sats)',
|
||||||
service_fee_max_hint: 'Taxa máxima de serviço a cobrar em (sats)',
|
service_fee_max_hint: 'Taxa máxima de serviço a cobrar em (sats)',
|
||||||
fee_wallet: 'Carteira de Taxas',
|
fee_wallet: 'Carteira de Taxas',
|
||||||
@@ -391,6 +671,8 @@ window.localisation.br = {
|
|||||||
'Desativar Taxa de Serviço para Pagamentos Internos Lightning',
|
'Desativar Taxa de Serviço para Pagamentos Internos Lightning',
|
||||||
ui_management: 'Gerenciamento de UI',
|
ui_management: 'Gerenciamento de UI',
|
||||||
ui_site_title: 'Título do Site',
|
ui_site_title: 'Título do Site',
|
||||||
|
ui_changing_remove_lnbits_elements:
|
||||||
|
'(alterar removerá os elementos LNbits na página inicial e rodapé)',
|
||||||
ui_site_tagline: 'Tagline do site',
|
ui_site_tagline: 'Tagline do site',
|
||||||
ui_elements_enable: 'Habilitar elementos na página inicial',
|
ui_elements_enable: 'Habilitar elementos na página inicial',
|
||||||
ui_elements_disable: 'Desativar elementos na página inicial',
|
ui_elements_disable: 'Desativar elementos na página inicial',
|
||||||
@@ -399,11 +681,27 @@ window.localisation.br = {
|
|||||||
ui_site_description: 'Descrição do Site',
|
ui_site_description: 'Descrição do Site',
|
||||||
ui_site_description_hint: 'Use texto simples, Markdown ou HTML bruto',
|
ui_site_description_hint: 'Use texto simples, Markdown ou HTML bruto',
|
||||||
ui_default_wallet_name: 'Nome Padrão da Carteira',
|
ui_default_wallet_name: 'Nome Padrão da Carteira',
|
||||||
|
ui_default_theme: 'Tema Padrão',
|
||||||
|
wallet_featured_button_label: 'Carteira Rótulo do Botão em Destaque',
|
||||||
|
wallet_featured_button_label_hint:
|
||||||
|
'Mostrar botão em destaque na página inicial da carteira',
|
||||||
|
wallet_featured_button_url: 'URL do Botão em Destaque',
|
||||||
|
wallet_featured_button_url_hint:
|
||||||
|
'Ao clicar, o botão abrirá este URL. Deixe em branco para ocultar o botão.',
|
||||||
|
wallet_featured_button_icon: 'Ícone do Botão em Destaque',
|
||||||
|
wallet_featured_button_icon_hint:
|
||||||
|
'Ícone mostrado no botão de destaque (verifique os ícones quasar)',
|
||||||
lnbits_wallet: 'Carteira LNbits',
|
lnbits_wallet: 'Carteira LNbits',
|
||||||
denomination: 'Denominação',
|
denomination: 'Denominação',
|
||||||
denomination_hint: 'O nome para o token FakeWallet',
|
denomination_hint: 'O nome para o token FakeWallet',
|
||||||
|
denomination_error: 'A denominação deve ter 3 caracteres ou `sats`.',
|
||||||
ui_qr_code_logo: 'Logo do QR Code',
|
ui_qr_code_logo: 'Logo do QR Code',
|
||||||
ui_qr_code_logo_hint: 'URL para imagem de logo no código QR',
|
ui_qr_code_logo_hint: 'URL para imagem de logo no código QR',
|
||||||
|
ui_apple_touch_icon: 'Ícone de Toque da Apple',
|
||||||
|
ui_apple_touch_icon_hint: 'URL do ícone de toque da Apple',
|
||||||
|
ui_custom_image: 'Imagem Personalizada',
|
||||||
|
ui_custom_image_label: 'URL para imagem personalizada',
|
||||||
|
ui_custom_image_hint: 'Imagem exibida na página inicial/login',
|
||||||
ui_custom_badge: 'Distintivo Personalizado',
|
ui_custom_badge: 'Distintivo Personalizado',
|
||||||
ui_custom_badge_label:
|
ui_custom_badge_label:
|
||||||
"Distintivo Personalizado 'USE COM CUIDADO - a carteira LNbits ainda está em BETA'",
|
"Distintivo Personalizado 'USE COM CUIDADO - a carteira LNbits ainda está em BETA'",
|
||||||
@@ -426,14 +724,95 @@ window.localisation.br = {
|
|||||||
admin_users_label: 'ID do Usuário',
|
admin_users_label: 'ID do Usuário',
|
||||||
allowed_users: 'Usuários Permitidos',
|
allowed_users: 'Usuários Permitidos',
|
||||||
allowed_users_hint: 'Somente esses usuários podem usar o LNbits',
|
allowed_users_hint: 'Somente esses usuários podem usar o LNbits',
|
||||||
|
allowed_users_hint_feature: 'Somente estes usuários podem usar {feature}',
|
||||||
allowed_users_label: 'ID do Usuário',
|
allowed_users_label: 'ID do Usuário',
|
||||||
allow_creation_user: 'Permitir a criação de novos usuários',
|
allow_creation_user: 'Permitir a criação de novos usuários',
|
||||||
allow_creation_user_desc:
|
allow_creation_user_desc:
|
||||||
'Permitir a criação de novos usuários na página de índice',
|
'Permitir a criação de novos usuários na página de índice',
|
||||||
|
require_user_activation: 'Requer ativação do usuário',
|
||||||
|
require_user_activation_desc:
|
||||||
|
'Novos usuários serão ativados somente após passarem por um dos métodos de confirmação. Administradores podem ativar usuários manualmente a partir do painel de administração.',
|
||||||
|
reusable_activation_code: 'Código de ativação reutilizável',
|
||||||
|
reusable_activation_code_label: 'Código de ativação reutilizável',
|
||||||
|
reusable_activation_code_hint:
|
||||||
|
'Este código de ativação pode ser usado várias vezes por diferentes usuários.',
|
||||||
|
one_time_activation_code: 'Códigos de ativação única',
|
||||||
|
one_time_activation_code_label: 'Adicionar código de ativação',
|
||||||
|
one_time_activation_code_hint:
|
||||||
|
'Lista de códigos de ativação únicos. Cada código pode ser usado apenas uma vez, depois será removido da lista.',
|
||||||
|
invitation_code: 'Código de Convite',
|
||||||
|
invitation_code_hint: 'O código de convite que você recebeu.',
|
||||||
|
email_confirmation_hint:
|
||||||
|
'Endereço de e-mail para enviar o código de confirmação.',
|
||||||
|
nostr_identifier: 'Identificador Nostr',
|
||||||
|
nostr_identifier_hint:
|
||||||
|
'Identificador nostr nip5 ou <npub> para enviar o código de confirmação.',
|
||||||
|
new_user_not_allowed: 'O registro está desativado.',
|
||||||
|
start_user_impersonation: 'Personificar este usuário',
|
||||||
|
stop_user_impersonation: 'Parar a Personificação do Usuário',
|
||||||
components: 'Componentes',
|
components: 'Componentes',
|
||||||
long_running_endpoints: 'Top 5 Endpoints de Longa Execução',
|
long_running_endpoints: 'Top 5 Endpoints de Longa Execução',
|
||||||
http_request_methods: 'Métodos de Requisição HTTP',
|
http_request_methods: 'Métodos de Requisição HTTP',
|
||||||
http_response_codes: 'Códigos de Resposta HTTP',
|
http_response_codes: 'Códigos de Resposta HTTP',
|
||||||
request_details: 'Detalhes do Pedido',
|
request_details: 'Detalhes do Pedido',
|
||||||
http_request_details: 'Detalhes da Requisição HTTP'
|
http_request_details: 'Detalhes da Requisição HTTP',
|
||||||
|
payment_details: 'Detalhes do Pagamento',
|
||||||
|
payment_details_desc: 'Informações detalhadas sobre o pagamento',
|
||||||
|
payments: 'Pagamentos',
|
||||||
|
payment_show_internal: 'Mostrar Pagamentos Internos',
|
||||||
|
payment_chart_flow: 'Fluxo de Pagamento Mensal',
|
||||||
|
payment_chart_status: 'Status do Pagamento',
|
||||||
|
payment_chart_tx_per_wallet: 'Transações por Carteira (saldo/contagem)',
|
||||||
|
payment_details_back: 'Voltar para Pagamentos',
|
||||||
|
payment_chart_tags: 'Pagamentos por Tags',
|
||||||
|
payments_balance_in_out: 'Entradas/Saídas de Saldo',
|
||||||
|
payments_count_in_out: 'Contar Entrada/Saída',
|
||||||
|
payments_status_chart: 'Gráfico de Status',
|
||||||
|
payments_tag_chart: 'Gráfico de Marcadores',
|
||||||
|
payments_balance_chart: 'Gráfico de Saldo',
|
||||||
|
payments_wallets_chart: 'Gráfico de Carteiras',
|
||||||
|
payments_balance_in_out_chart: 'Gráfico de Entrada/Saída de Saldo',
|
||||||
|
payments_count_in_out_chart: 'Gráfico de Entrada/Saída',
|
||||||
|
reset_wallet_keys: 'Redefinir Chaves',
|
||||||
|
reset_wallet_keys_desc:
|
||||||
|
'Redefina as chaves de API para esta carteira. Isso invalidará as chaves atuais e gerará novas.',
|
||||||
|
view_list: 'Visualizar carteiras como lista',
|
||||||
|
view_column: 'Visualizar carteiras como linhas',
|
||||||
|
filter_payments: 'Filtrar pagamentos',
|
||||||
|
filter_labels: 'Rótulos de filtro',
|
||||||
|
filter_date: 'Filtrar por data',
|
||||||
|
websocket_example: 'Exemplo de Websocket',
|
||||||
|
client_id: 'ID do Cliente',
|
||||||
|
secret_key: 'Chave Secreta',
|
||||||
|
signing_secret: 'Segredo de Assinatura',
|
||||||
|
signing_secret_hint:
|
||||||
|
'Segredo de assinatura para o webhook. As mensagens serão assinadas com este segredo.',
|
||||||
|
webhook_id: 'ID do Webhook',
|
||||||
|
webhook_id_hint:
|
||||||
|
'ID do webhook do PayPal usado para verificar eventos recebidos.',
|
||||||
|
webhook_paypal_description:
|
||||||
|
'No lado do PayPal, configure um webhook apontando para o seu servidor LNbits.',
|
||||||
|
callback_success_url: 'URL de Sucesso de Callback',
|
||||||
|
callback_success_url_hint:
|
||||||
|
'O usuário será redirecionado para este URL após o pagamento ser bem-sucedido.',
|
||||||
|
connected: 'Conectado',
|
||||||
|
not_connected: 'Não Conectado',
|
||||||
|
free: 'Grátis',
|
||||||
|
paid: 'Pago',
|
||||||
|
funding_source_retries: 'Máximo de tentativas',
|
||||||
|
funding_source_retries_desc:
|
||||||
|
'Número máximo de tentativas para fontes de financiamento, antes de voltar para VoidWallet.',
|
||||||
|
add_label: 'Adicionar Rótulo',
|
||||||
|
label: 'Etiqueta',
|
||||||
|
labels: 'Rótulos',
|
||||||
|
label_filter: 'Filtro de Rótulo',
|
||||||
|
no_labels_defined: 'Ainda não há rótulos definidos',
|
||||||
|
manage_labels: 'Gerenciar Etiquetas',
|
||||||
|
update_label: 'Atualizar Rótulo',
|
||||||
|
delete_label: 'Excluir Rótulo',
|
||||||
|
add_remove_labels: 'Adicionar ou Remover Rótulos',
|
||||||
|
payment_labels_updated: 'Rótulos de pagamento atualizados',
|
||||||
|
color: 'Cor',
|
||||||
|
sort: 'Ordenar',
|
||||||
|
sort_by: 'Ordenar por'
|
||||||
}
|
}
|
||||||
|
|||||||
+143
-90
@@ -6,7 +6,7 @@ window.localisation.en = {
|
|||||||
funding: 'Funding',
|
funding: 'Funding',
|
||||||
users: 'Users',
|
users: 'Users',
|
||||||
audit: 'Audit',
|
audit: 'Audit',
|
||||||
api_watch: 'Api Watch',
|
api_watch: 'API Watch',
|
||||||
apps: 'Apps',
|
apps: 'Apps',
|
||||||
channels: 'Channels',
|
channels: 'Channels',
|
||||||
transactions: 'Transactions',
|
transactions: 'Transactions',
|
||||||
@@ -171,6 +171,7 @@ window.localisation.en = {
|
|||||||
drop_db: 'Remove Data',
|
drop_db: 'Remove Data',
|
||||||
enable: 'Enable',
|
enable: 'Enable',
|
||||||
enabled: 'Enabled',
|
enabled: 'Enabled',
|
||||||
|
disabled: 'Disabled',
|
||||||
pay_to_enable: 'Pay To Enable',
|
pay_to_enable: 'Pay To Enable',
|
||||||
enable_extension_details: 'Enable extension for current user',
|
enable_extension_details: 'Enable extension for current user',
|
||||||
disable: 'Disable',
|
disable: 'Disable',
|
||||||
@@ -178,6 +179,8 @@ window.localisation.en = {
|
|||||||
installed: 'Installed',
|
installed: 'Installed',
|
||||||
activated: 'Activated',
|
activated: 'Activated',
|
||||||
deactivated: 'Deactivated',
|
deactivated: 'Deactivated',
|
||||||
|
activate: 'Activate',
|
||||||
|
deactivate: 'Deactivate',
|
||||||
release_notes: 'Release Notes',
|
release_notes: 'Release Notes',
|
||||||
activate_extension_details: 'Make extension available/unavailable for users',
|
activate_extension_details: 'Make extension available/unavailable for users',
|
||||||
featured: 'Featured',
|
featured: 'Featured',
|
||||||
@@ -186,6 +189,8 @@ window.localisation.en = {
|
|||||||
only_admins_can_create_extensions:
|
only_admins_can_create_extensions:
|
||||||
'Only admin accounts can create extensions',
|
'Only admin accounts can create extensions',
|
||||||
admin_only: 'Admin Only',
|
admin_only: 'Admin Only',
|
||||||
|
make_user_admin: 'Make User Admin',
|
||||||
|
revoke_admin: 'Revoke Admin',
|
||||||
new_version: 'New Version',
|
new_version: 'New Version',
|
||||||
reviews_url: 'Reviews URL',
|
reviews_url: 'Reviews URL',
|
||||||
reviews_url_label: 'Reviews server URL',
|
reviews_url_label: 'Reviews server URL',
|
||||||
@@ -266,12 +271,16 @@ window.localisation.en = {
|
|||||||
update: 'Update',
|
update: 'Update',
|
||||||
update_available: 'Update {version} available!',
|
update_available: 'Update {version} available!',
|
||||||
funding_sources: 'Funding Sources',
|
funding_sources: 'Funding Sources',
|
||||||
|
funding_source: 'Funding Source',
|
||||||
|
requires_server_restart:
|
||||||
|
'Changing these settings requires a server restart to take effect.',
|
||||||
|
funding_source_info: 'Select the active funding wallet',
|
||||||
latest_update: 'You are on the latest version {version}.',
|
latest_update: 'You are on the latest version {version}.',
|
||||||
notifications: 'Notifications',
|
notifications: 'Notifications',
|
||||||
notifications_configure: 'Configure Notifications',
|
notifications_configure: 'Configure Notifications',
|
||||||
notifications_nostr_config: 'Nostr Configuration',
|
notifications_nostr_config: 'Nostr Configuration',
|
||||||
notifications_enable_nostr: 'Enable Nostr',
|
notifications_enable_nostr: 'Enable Nostr',
|
||||||
notifications_enable_nostr_desc: 'Send notfications over Nostr',
|
notifications_enable_nostr_desc: 'Send notifications over Nostr',
|
||||||
notifications_nostr_private_key: 'Nostr Private Key',
|
notifications_nostr_private_key: 'Nostr Private Key',
|
||||||
notifications_nostr_private_key_desc:
|
notifications_nostr_private_key_desc:
|
||||||
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
||||||
@@ -280,11 +289,11 @@ window.localisation.en = {
|
|||||||
'Nip5 identifier to send notifications to',
|
'Nip5 identifier to send notifications to',
|
||||||
notifications_nostr_identifiers: 'Nostr Identifiers',
|
notifications_nostr_identifiers: 'Nostr Identifiers',
|
||||||
notifications_nostr_identifiers_desc:
|
notifications_nostr_identifiers_desc:
|
||||||
'List of identifiers to send notifications to',
|
'List of identifiers to send notifications to.',
|
||||||
|
|
||||||
notifications_telegram_config: 'Telegram Configuration',
|
notifications_telegram_config: 'Telegram Configuration',
|
||||||
notifications_enable_telegram: 'Enable Telegram',
|
notifications_enable_telegram: 'Enable Telegram',
|
||||||
notifications_enable_telegram_desc: 'Send notfications over Telegram',
|
notifications_enable_telegram_desc: 'Send notifications over Telegram',
|
||||||
notifications_telegram_access_token: 'Access Token',
|
notifications_telegram_access_token: 'Access Token',
|
||||||
notifications_telegram_access_token_desc: 'Access token for the bot',
|
notifications_telegram_access_token_desc: 'Access token for the bot',
|
||||||
notifications_chat_id: 'Telegram Chat ID',
|
notifications_chat_id: 'Telegram Chat ID',
|
||||||
@@ -313,15 +322,15 @@ window.localisation.en = {
|
|||||||
|
|
||||||
notification_settings_update: 'Settings updated',
|
notification_settings_update: 'Settings updated',
|
||||||
notification_settings_update_desc:
|
notification_settings_update_desc:
|
||||||
'Notify when server settings have been updated',
|
'Send a notification when server settings have been updated',
|
||||||
|
|
||||||
notification_server_start_stop: 'Server Start/Stop',
|
notification_server_start_stop: 'Server Start/Stop',
|
||||||
notification_server_start_stop_desc:
|
notification_server_start_stop_desc:
|
||||||
'Notify when the server has been started/stopped',
|
'Send a notification when the server has been started/stopped',
|
||||||
|
|
||||||
notification_watchdog_limit: 'Watchdog Limit Notification',
|
notification_watchdog_limit: 'Watchdog Limit Notification',
|
||||||
notification_watchdog_limit_desc:
|
notification_watchdog_limit_desc:
|
||||||
'Notify when the watchdog limit has been reached (does not affect the funding source)',
|
'Send a notification when the watchdog limit has been reached (does not affect the funding source)',
|
||||||
|
|
||||||
notification_server_status: 'Server Status',
|
notification_server_status: 'Server Status',
|
||||||
notification_server_status_desc:
|
notification_server_status_desc:
|
||||||
@@ -329,29 +338,31 @@ window.localisation.en = {
|
|||||||
|
|
||||||
notification_incoming_payment: 'Incoming Payments',
|
notification_incoming_payment: 'Incoming Payments',
|
||||||
notification_incoming_payment_desc:
|
notification_incoming_payment_desc:
|
||||||
'Notify when a wallet has received a payment above the specified amount (sats)',
|
'Send a notification when a wallet has received a payment above the specified amount (sats)',
|
||||||
|
|
||||||
notification_outgoing_payment: 'Outgoing Payments',
|
notification_outgoing_payment: 'Outgoing Payments',
|
||||||
notification_outgoing_payment_desc:
|
notification_outgoing_payment_desc:
|
||||||
'Notify when a wallet has sent a payment above the specified amount (sats)',
|
'Send a notification when a wallet has sent a payment above the specified amount (sats)',
|
||||||
|
|
||||||
notification_credit_debit: 'Credit / Debit',
|
notification_credit_debit: 'Credit / Debit',
|
||||||
notification_credit_debit_desc:
|
notification_credit_debit_desc:
|
||||||
'Notify when a wallet has been credited/debited by the superuser',
|
'Send a notification when a wallet has been credited/debited by the superuser',
|
||||||
|
|
||||||
notification_balance_delta_changed: 'Balance Delta Changed',
|
notification_balance_delta_changed: 'Balance Delta Changed',
|
||||||
notification_balance_delta_changed_desc:
|
notification_balance_delta_changed_desc:
|
||||||
'Notify when the difference between the node balance and the LNbits balance has changed by more than the specified amount (in sats). Set to 0 to disable. This runs every minute.',
|
'Send a notification when the difference between the node balance and the LNbits balance has changed by more than the specified amount (in sats). Set to 0 to disable. This runs every minute.',
|
||||||
|
|
||||||
enable_watchdog: 'Enable Watchdog Switch',
|
watchdog_introduction:
|
||||||
|
"Watchdog is a feature that allows you to automatically switch the LNbits funding source to VoidWallet if your node balance is lower than the LNbits balance by a certain threshold. This can help prevent overspending and keep your node's funds safe.",
|
||||||
|
enable_watchdog: 'Enable Watchdog',
|
||||||
enable_watchdog_desc:
|
enable_watchdog_desc:
|
||||||
'If enabled it will change your funding source to VoidWallet automatically if your balance is lower than the LNbits balance. You will need to enable manually after an update.',
|
'You will need to re-enable this manually after an update.',
|
||||||
watchdog_interval: 'Watchdog Interval',
|
watchdog_interval: 'Watchdog Check Interval',
|
||||||
watchdog_interval_desc:
|
watchdog_interval_desc:
|
||||||
'How often the background task should check for a killswitch signal in the watchdog delta [node_balance - lnbits_balance] (in minutes).',
|
'How often LNbits should check for a killswitch signal in the watchdog threshold delta value [node_balance - lnbits_balance] (in minutes).',
|
||||||
watchdog_delta: 'Watchdog Delta',
|
watchdog_delta: 'Watchdog Threshold Delta',
|
||||||
watchdog_delta_desc:
|
watchdog_delta_desc:
|
||||||
'Limit before killswitch changes funding source to VoidWallet [lnbits_balance - node_balance > delta]',
|
"The LNbit's > Node balance delta threshold. If this threshold is exceeded, the funding source is changed to VoidWallet.",
|
||||||
status: 'Status',
|
status: 'Status',
|
||||||
notification_source: 'Notification Source',
|
notification_source: 'Notification Source',
|
||||||
notification_source_label:
|
notification_source_label:
|
||||||
@@ -362,12 +373,12 @@ window.localisation.en = {
|
|||||||
releases: 'Releases',
|
releases: 'Releases',
|
||||||
watchdog: 'Watchdog',
|
watchdog: 'Watchdog',
|
||||||
server_logs: 'Server Logs',
|
server_logs: 'Server Logs',
|
||||||
ip_blocker: 'IP Blocker',
|
ip_blocker: 'IP Blacklist/Whitelist',
|
||||||
security: 'Security',
|
security: 'Security',
|
||||||
security_tools: 'Security tools',
|
security_tools: 'Security Tools',
|
||||||
block_access_hint: 'Block access by IP',
|
block_access_hint: 'Block access by IP',
|
||||||
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 an IP address and press enter',
|
||||||
rate_limiter: 'Rate Limiter',
|
rate_limiter: 'Rate Limiter',
|
||||||
callback_url_rules: 'Callback URL Rules',
|
callback_url_rules: 'Callback URL Rules',
|
||||||
enter_callback_url_rule: 'Enter URL rule as regex and hit enter',
|
enter_callback_url_rule: 'Enter URL rule as regex and hit enter',
|
||||||
@@ -381,14 +392,16 @@ window.localisation.en = {
|
|||||||
wallet_max_ballance: 'Wallet max balance in sats (0 to disable)',
|
wallet_max_ballance: 'Wallet max balance in sats (0 to disable)',
|
||||||
wallet_limit_secs_between_trans:
|
wallet_limit_secs_between_trans:
|
||||||
'Min secs between transactions per wallet (0 to disable)',
|
'Min secs between transactions per wallet (0 to disable)',
|
||||||
only_incoming_payments_allowed: 'Only incoming payments allowed',
|
only_incoming_payments_allowed: 'Allow incoming payments only',
|
||||||
disable_outgoing_payments: 'Disable outgoing payments',
|
disable_outgoing_payments: 'Disable outgoing payments',
|
||||||
number_of_requests: 'Number of requests',
|
number_of_requests: 'Number of requests to allow',
|
||||||
|
number_of_requests_hint:
|
||||||
|
'Number of requests to allow per "time unit" for the rate limiter. Set to 0 to disable.',
|
||||||
time_unit: 'Time unit',
|
time_unit: 'Time unit',
|
||||||
minute: 'minute',
|
minute: 'Minute',
|
||||||
settings: 'Settings',
|
settings: 'Settings',
|
||||||
second: 'second',
|
second: 'Second',
|
||||||
hour: 'hour',
|
hour: 'Hour',
|
||||||
disable_server_log: 'Disable Server Log',
|
disable_server_log: 'Disable Server Log',
|
||||||
enable_server_log: 'Enable Server Log',
|
enable_server_log: 'Enable Server Log',
|
||||||
coming_soon: 'Feature coming soon',
|
coming_soon: 'Feature coming soon',
|
||||||
@@ -460,6 +473,12 @@ window.localisation.en = {
|
|||||||
api_token_id: 'Token Id',
|
api_token_id: 'Token Id',
|
||||||
toggle_gradient: 'Toggle Gradient',
|
toggle_gradient: 'Toggle Gradient',
|
||||||
gradient_background: 'Gradient Background',
|
gradient_background: 'Gradient Background',
|
||||||
|
rounded_ui: 'Rounded Cards & Buttons',
|
||||||
|
toggle_rounded_ui: 'Toggle rounded corners for cards and buttons',
|
||||||
|
card_gradient: 'Card Gradient',
|
||||||
|
toggle_card_gradient: 'Toggle gradient on cards',
|
||||||
|
card_shadow: 'Card Shadow',
|
||||||
|
toggle_card_shadow: 'Toggle shadow on cards',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
assets: 'Assets',
|
assets: 'Assets',
|
||||||
max_asset_size_mb: 'Max Asset Size (MB)',
|
max_asset_size_mb: 'Max Asset Size (MB)',
|
||||||
@@ -518,36 +537,34 @@ window.localisation.en = {
|
|||||||
hash: 'Hash: ',
|
hash: 'Hash: ',
|
||||||
welcome_lnbits: 'Welcome to LNbits',
|
welcome_lnbits: 'Welcome to LNbits',
|
||||||
setup_su_account: 'Set up the Superuser account below.',
|
setup_su_account: 'Set up the Superuser account below.',
|
||||||
|
first_install_token: 'First Install Token',
|
||||||
create_ticker_converter: 'Create Currency Ticker Converter',
|
create_ticker_converter: 'Create Currency Ticker Converter',
|
||||||
enable_audit: 'Enable Audit',
|
enable_audit: 'Enable Audit',
|
||||||
recommended: 'Recommended',
|
recommended: 'Recommended',
|
||||||
audit_desc: 'Record HTTP requests according with the specified filters',
|
audit_desc: 'Log HTTP requests according to the filters specified below.',
|
||||||
audit_record_req: 'Record Request Body',
|
audit_record_req: 'Log Request Body',
|
||||||
audit_record_warning: 'Warning: ',
|
audit_record_warning: 'Warning!',
|
||||||
audit_record_req_warning_1:
|
audit_record_req_warning_1: 'Sensitive data (like passwords) will be logged.',
|
||||||
'confidential data (like passwords) will be logged.',
|
audit_record_req_warning_2:
|
||||||
audit_record_req_warning_2: 'the request body can have large size.',
|
'The request body can be large. This can fill up your logs quickly.',
|
||||||
audit_record_use: 'Use it with caution.',
|
audit_record_use: 'Use this with caution!',
|
||||||
audit_ip: 'Record IP Address',
|
audit_ip: 'Log IP Address',
|
||||||
audit_ip_desc: 'Record the IP address of the client',
|
audit_ip_desc: 'Log the IP address of users making requests to LNbits.',
|
||||||
audit_path_params: 'Record Path Parameters',
|
audit_path_params: 'Log Path Parameters',
|
||||||
audit_query_params: 'Record Query Parameters',
|
audit_query_params: 'Log Query Parameters',
|
||||||
audit_http_methods: 'Include HTTP Methods',
|
audit_http_methods: 'Include HTTP Methods',
|
||||||
audit_http_methods_hint:
|
audit_http_methods_hint:
|
||||||
'List of HTTP methods to be included. Empty lists means all.',
|
'List of HTTP methods to be logged. No value means all methods will be logged.',
|
||||||
audit_http_methods_label: 'HTTP Methods',
|
audit_http_methods_label: 'HTTP Methods to Log',
|
||||||
audit_resp_codes: 'Include HTTP Response Codes',
|
|
||||||
audit_resp_codes_hint:
|
audit_resp_codes_hint:
|
||||||
'List of HTTP codes to be included (regex match). Empty lists means all. Eg: 4.*, 5.*',
|
'List of HTTP codes to be included (regex match). Empty lists means all. Eg: 4.*, 5.*',
|
||||||
audit_resp_codes_label: 'HTTP Response code (regex)',
|
audit_resp_codes_label: 'HTTP Response Codes to Log (regex)',
|
||||||
audit_paths: 'Include Paths',
|
|
||||||
audit_paths_hint:
|
audit_paths_hint:
|
||||||
'List of paths to be included (regex match). Empty list means all.',
|
'List of paths to be included (regex match). Empty list means all.',
|
||||||
audit_paths_label: 'HTTP Path (regex)',
|
audit_paths_label: 'HTTP Paths to Log (regex)',
|
||||||
audit_paths_exclude: 'Exclude Paths',
|
|
||||||
audit_paths_exclude_hint:
|
audit_paths_exclude_hint:
|
||||||
'List of paths to be excluded (regex match). Empty list means none.',
|
'List of paths to be excluded (regex match). Empty list means none.',
|
||||||
audit_paths_exclude_label: 'HTTP Path (regex)',
|
audit_paths_exclude_label: 'HTTP Paths to Exclude from Logging (regex)',
|
||||||
exchange_providers: 'Exchange Providers',
|
exchange_providers: 'Exchange Providers',
|
||||||
admin_extensions: 'Admin Extensions',
|
admin_extensions: 'Admin Extensions',
|
||||||
admin_extensions_label: 'Admin extensions',
|
admin_extensions_label: 'Admin extensions',
|
||||||
@@ -567,41 +584,47 @@ window.localisation.en = {
|
|||||||
misc_disable_extensions_builder_label:
|
misc_disable_extensions_builder_label:
|
||||||
'Enable Extensions Builder for non admin users.',
|
'Enable Extensions Builder for non admin users.',
|
||||||
misc_hide_api: 'Hide API',
|
misc_hide_api: 'Hide API',
|
||||||
misc_hide_api_label: 'Hides wallet api, extensions can choose to honor',
|
misc_hide_api_label: 'Hides wallet API, extensions can choose to honor',
|
||||||
wallets_management: 'Wallets Management',
|
wallets_management: 'Wallets Management',
|
||||||
funding_source_info: 'Funding Source Info',
|
funding_source_info: 'Funding Source Information',
|
||||||
funding_source: 'Funding Source: {wallet_class}',
|
funding_source: 'Funding source: {wallet_class}',
|
||||||
node_balance: 'Node Balance: {balance} sats',
|
node_balance: 'Node balance: {balance} sats',
|
||||||
lnbits_balance: 'LNbits Balance: {balance} sats',
|
lnbits_balance: 'LNbits balance: {balance} sats',
|
||||||
funding_reserve_percent: 'Reserve Percent: {percent} %',
|
funding_reserve_percent: 'Funding reserve percentage: {percent} %',
|
||||||
node_management: 'Node Management',
|
node_management: 'Node Management',
|
||||||
node_management_not_supported:
|
node_management_not_supported:
|
||||||
'Node Management not supported by active funding source',
|
'Node management is not supported by the active funding source',
|
||||||
toggle_node_ui: 'Node UI',
|
toggle_node_ui: 'Node UI',
|
||||||
toggle_public_node_ui: 'Public Node UI',
|
toggle_public_node_ui: 'Public Node UI',
|
||||||
toggle_transactions_node_ui: 'Transactions Tab (Disable on large CLN nodes)',
|
toggle_transactions_node_ui: 'Transactions Tab (Disable on large CLN nodes)',
|
||||||
invoice_expiry: 'Invoice Expiry',
|
invoice_expiry: 'Invoice Expiry',
|
||||||
invoice_expiry_label: 'Invoice expiry (seconds)',
|
routing_fee_reserve_calculations: 'Routing Fee Reserve Calculations',
|
||||||
fee_reserve: 'Fee Reserve',
|
routing_fee_reserve_calculations_desc:
|
||||||
fee_reserve_percent: 'Fee Reserve Percent',
|
'LNbits sets aside a “reserve amount” for each payment to cover routing fees. The maximum routing fee passed to the funding source is whichever is higher: the <strong>minimum routing fee reserve</strong> or the <strong>routing fee reserve percentage</strong>.',
|
||||||
fee_reserve_msats: 'Reserve fee in msats',
|
millisats: 'millisats',
|
||||||
reserve_fee_in_percent: 'Reserve fee in percent',
|
fee_reserve: 'Minimum Routing Fee Reserve',
|
||||||
payment_wait_time: 'Payment Wait Time (sec)',
|
fee_reserve_percent: 'Routing Fee Reserve Percentage',
|
||||||
|
fee_reserve_min_hint:
|
||||||
|
'The minimum fee reserved per payment.<br>This acts as a floor - the maximum allowed routing fee will never be lower than this value regardless of payment size.',
|
||||||
|
fee_reserve_percent_hint:
|
||||||
|
'The percentage of the payment amount to reserve for routing fees.',
|
||||||
|
payment_timeouts: 'Payment Timeouts',
|
||||||
|
payment_wait_time: 'Payment Wait Time',
|
||||||
|
seconds: 'seconds',
|
||||||
payment_wait_time_desc:
|
payment_wait_time_desc:
|
||||||
'Wait time before marking an outgoing payment as pending. Default: 5s; raise for slow-settling invoices.',
|
'Wait time before marking an outgoing payment as pending. Default: 5s; raise for slow-settling invoices.',
|
||||||
payment_wait_time_tooltip:
|
payment_wait_time_tooltip:
|
||||||
'Controls how long LNbits waits for an outgoing payment attempt to confirm before marking it as pending. Higher values help when paying slow-settling invoices (e.g., HODL invoices, Boltz). The payment will be rechecked later and updated automatically or manually.',
|
'Controls how long LNbits waits for an outgoing payment attempt to confirm before marking it as pending. Higher values help when paying slow-settling invoices (e.g., HODL invoices, Boltz). The payment will be rechecked later and updated automatically or manually.',
|
||||||
server_management: 'Server Management',
|
server_management: 'Server Management',
|
||||||
base_url: 'Base URL',
|
base_url_label: 'Base URL of the server',
|
||||||
base_url_label: 'Static/Base url for the server',
|
|
||||||
authentication: 'Authentication',
|
authentication: 'Authentication',
|
||||||
auth_token_expiry_label: 'Token expire minutes',
|
auth_token_expiry_label: 'Token expiry (minutes)',
|
||||||
auth_token_expiry_hint: 'Time in minutes until the token expires',
|
auth_token_expiry_hint: 'Time in minutes until the token expires',
|
||||||
auth_authentication_cache_label: 'Cache time (minutes)',
|
auth_authentication_cache_label: 'Cache time (minutes)',
|
||||||
auth_authentication_cache_hint:
|
auth_authentication_cache_hint:
|
||||||
'Time in minutes to cache successful authentication (0 to disable)',
|
'Time in minutes to cache successful authentication (0 to disable)',
|
||||||
auth_allowed_methods_label: 'Allowed authorization methods',
|
auth_allowed_methods_label: 'Allowed authorization methods',
|
||||||
auth_allowed_methods_hint: 'Select authorization methods',
|
auth_allowed_methods_hint: 'Select allowed authorization methods',
|
||||||
auth_nostr_label: 'Nostr Request URL',
|
auth_nostr_label: 'Nostr Request URL',
|
||||||
auth_nostr_hint: 'Absolute URL that the clients will use to login.',
|
auth_nostr_hint: 'Absolute URL that the clients will use to login.',
|
||||||
auth_google_ci_label: 'Google Client ID',
|
auth_google_ci_label: 'Google Client ID',
|
||||||
@@ -621,31 +644,27 @@ window.localisation.en = {
|
|||||||
auth_keycloak_custom_icon_label: 'Keycloak Custom Icon (URL)',
|
auth_keycloak_custom_icon_label: 'Keycloak Custom Icon (URL)',
|
||||||
currency_settings: 'Currency Settings',
|
currency_settings: 'Currency Settings',
|
||||||
allowed_currencies: 'Allowed Currencies',
|
allowed_currencies: 'Allowed Currencies',
|
||||||
allowed_currencies_hint: 'Limit the number of available fiat currencies',
|
allowed_currencies_hint:
|
||||||
default_account_currency: 'Default Account Currency',
|
'Set the allowed fiat currencies for the exchange features',
|
||||||
default_account_currency_hint: 'Default currency for accounting',
|
default_account_currency: 'Default Accounting Currency',
|
||||||
|
default_account_currency_hint:
|
||||||
|
'The default currency to use for accounting features.',
|
||||||
min_incoming_payment_amount: 'Min Incoming Payment Amount',
|
min_incoming_payment_amount: 'Min Incoming Payment Amount',
|
||||||
min_incoming_payment_amount_desc:
|
min_incoming_payment_amount_desc:
|
||||||
'Minimum amount allowed for generating an invoice',
|
'Minimum amount allowed for generating an invoice',
|
||||||
max_incoming_payment_amount: 'Max Incoming Payment Amount',
|
max_incoming_payment_amount: 'Maximum Incoming Payment Amount',
|
||||||
max_incoming_payment_amount_desc:
|
max_incoming_payment_amount_desc:
|
||||||
'Maximum amount allowed for generating an invoice',
|
'Maximum amount allowed when generating an invoice',
|
||||||
max_outgoing_payment_amount: 'Max Outgoing Payment Amount',
|
max_outgoing_payment_amount: 'Maximum Outgoing Payment Amount',
|
||||||
max_outgoing_payment_amount_desc:
|
max_outgoing_payment_amount_desc:
|
||||||
'Maximum amount allowed for making a payment',
|
'Maximum amount allowed when making a payment',
|
||||||
|
service_fees: 'Service Fees',
|
||||||
service_fee: 'Service Fee',
|
service_fee: 'Service Fee',
|
||||||
service_fee_label: 'Service fee (%)',
|
service_fee_label: 'Service Fee Charged Per Transaction',
|
||||||
service_fee_hint: 'Fee charged per tx (%)',
|
service_fee_max_label: 'Maximum Service Fee Limit',
|
||||||
service_fee_max: 'Service Fee Max',
|
fee_wallet_label: 'Service Fee Wallet ID',
|
||||||
service_fee_max_label: 'Service fee max (sats)',
|
fee_wallet_hint: 'The ID of the wallet to which to send service funds',
|
||||||
service_fee_max_hint: 'Max service fee to charge in (sats)',
|
disable_fee: 'Disable Service Fees for Internal Payments',
|
||||||
fee_wallet: 'Fee Wallet',
|
|
||||||
fee_wallet_label: 'Fee wallet (wallet ID)',
|
|
||||||
fee_wallet_hint: 'Wallet ID to send funds to',
|
|
||||||
disable_fee: 'Disable Fee',
|
|
||||||
disable_fee_internal: 'Disable Service Fee for Internal Payments',
|
|
||||||
disable_fee_internal_desc:
|
|
||||||
'Disable Service Fee for Internal Lightning Payments',
|
|
||||||
ui_management: 'UI Management',
|
ui_management: 'UI Management',
|
||||||
ui_site_title: 'Site Title',
|
ui_site_title: 'Site Title',
|
||||||
ui_changing_remove_lnbits_elements:
|
ui_changing_remove_lnbits_elements:
|
||||||
@@ -658,32 +677,47 @@ window.localisation.en = {
|
|||||||
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
|
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
|
||||||
ui_default_wallet_name: 'Default Wallet Name',
|
ui_default_wallet_name: 'Default Wallet Name',
|
||||||
ui_default_theme: 'Default Theme',
|
ui_default_theme: 'Default Theme',
|
||||||
|
wallet_featured_button_title: 'Wallet - Featured Button',
|
||||||
|
wallet_featured_button_label: 'Wallet Featured Button Label',
|
||||||
|
wallet_featured_button_label_hint:
|
||||||
|
'Show featured button on the wallet homepage',
|
||||||
|
wallet_featured_button_url: 'Featured Button URL',
|
||||||
|
wallet_featured_button_url_hint:
|
||||||
|
'On click the button will open this URL. Leave empty to hide the button.',
|
||||||
|
wallet_featured_button_icon: 'Featured Button Icon',
|
||||||
|
wallet_featured_button_icon_hint:
|
||||||
|
'Icon shown on the featured button (Quasar icon name e.g. "bolt")',
|
||||||
lnbits_wallet: 'LNbits wallet',
|
lnbits_wallet: 'LNbits wallet',
|
||||||
denomination: 'Denomination',
|
denomination: 'Denomination',
|
||||||
denomination_hint: 'The name for the FakeWallet token',
|
denomination_hint: 'The name for the FakeWallet token',
|
||||||
denomination_error: 'Denomination must be 3 characters, or `sats`',
|
denomination_error: 'Denomination must be 3 characters, or `sats`',
|
||||||
ui_qr_code_logo: 'QR Code/Favicon Logo',
|
ui_qr_code_logo: 'QR Code/Favicon Logo',
|
||||||
ui_qr_code_logo_hint: 'QR code and favicon logo url',
|
ui_qr_code_logo_hint: 'QR code icon and favicon logo URL',
|
||||||
ui_apple_touch_icon: 'Apple Touch Icon',
|
ui_apple_touch_icon: 'Apple Touch Icon',
|
||||||
ui_apple_touch_icon_hint: 'Apple touch icon url',
|
ui_apple_touch_icon_hint: 'Apple touch icon URL',
|
||||||
ui_custom_image: 'Custom Image',
|
ui_custom_image: 'Custom Image',
|
||||||
ui_custom_image_label: 'URL to custom image',
|
ui_custom_image_label: 'URL to custom image',
|
||||||
ui_custom_image_hint: 'Image showed at homepage/login',
|
ui_custom_image_hint:
|
||||||
ui_custom_badge: 'Custom Badge',
|
'This image is shown on the LNbits homepage and login screen.',
|
||||||
|
ui_custom_badge_title: 'Custom Badge Settings',
|
||||||
|
ui_custom_badge_desc: 'Show a custom badge in the header of LNbits',
|
||||||
|
ui_custom_badge: 'Custom Badge Text',
|
||||||
ui_custom_badge_label: "Custom Badge 'USE WITH CAUTION'",
|
ui_custom_badge_label: "Custom Badge 'USE WITH CAUTION'",
|
||||||
ui_custom_badge_color_label: 'Custom Badge Color',
|
ui_custom_badge_color_label: 'Custom Badge Color',
|
||||||
themes: 'Themes',
|
themes: 'Themes',
|
||||||
themes_hint: 'Choose themes available for users',
|
themes_hint: 'Choose themes available for users',
|
||||||
custom_logo: 'Custom Logo',
|
custom_logo: 'Custom Logo',
|
||||||
custom_logo_hint: 'URL to logo image',
|
custom_logo_hint: 'URL to logo image',
|
||||||
ad_space_title: 'Ad Space Title',
|
ad_space_section_title: 'Advertisement Space',
|
||||||
ad_space_title_label: 'Supported by',
|
ad_space_section_desc:
|
||||||
|
'Configure the advertisement space on the wallet sidebar.',
|
||||||
|
ad_space_title: 'Advertisement Space Title',
|
||||||
|
ad_space_title_hint: 'Title shown above the advertisement space',
|
||||||
ad_slots: 'Advertisement Slots',
|
ad_slots: 'Advertisement Slots',
|
||||||
ad_slots_hint:
|
ad_slots_hint:
|
||||||
'Ad url and image filepaths in CSV format, extensions can choose to honor',
|
'Advertisement image filepaths in CSV format, extensions can choose to honor. Format: url;img_light_url;img_dark_url, url..',
|
||||||
ad_slots_label: 'url;img_light_url;img_dark_url, url...',
|
ads_enabled: 'Enable Advertisement',
|
||||||
ads_enabled: 'Ads Enabled',
|
ads_disabled: 'Disabled Advertisement',
|
||||||
ads_disabled: 'Ads Disabled',
|
|
||||||
user_management: 'User Management',
|
user_management: 'User Management',
|
||||||
admin_users: 'Admin Users',
|
admin_users: 'Admin Users',
|
||||||
admin_users_hint: 'Users with admin privileges',
|
admin_users_hint: 'Users with admin privileges',
|
||||||
@@ -694,6 +728,25 @@ window.localisation.en = {
|
|||||||
allowed_users_label: 'User ID',
|
allowed_users_label: 'User ID',
|
||||||
allow_creation_user: 'Allow creation of new users',
|
allow_creation_user: 'Allow creation of new users',
|
||||||
allow_creation_user_desc: 'Allow creation of new users on the index page',
|
allow_creation_user_desc: 'Allow creation of new users on the index page',
|
||||||
|
require_user_activation: 'Require user activation',
|
||||||
|
require_user_activation_desc:
|
||||||
|
'New users will be activated only after they pass one of the confirmation methods. Admins can activate users manually from the admin panel.',
|
||||||
|
reusable_activation_code: 'Reusable activation code',
|
||||||
|
reusable_activation_code_label: 'Reusable activation code',
|
||||||
|
reusable_activation_code_hint:
|
||||||
|
'This activation code can be used multiple times by different users.',
|
||||||
|
one_time_activation_code: 'One-time activation codes',
|
||||||
|
one_time_activation_code_label: 'Add activation code',
|
||||||
|
one_time_activation_code_hint:
|
||||||
|
'List of one-time activation codes. Each code can be used only once, then will be reomved from the list.',
|
||||||
|
invitation_code: 'Invitation Code',
|
||||||
|
invitation_code_hint: 'The invitation code that you have received.',
|
||||||
|
email: 'Email',
|
||||||
|
email_confirmation_hint: 'Email address to send the confirmation code to.',
|
||||||
|
nostr_identifier: 'Nostr Identifier',
|
||||||
|
nostr_identifier_hint:
|
||||||
|
'Nostr nip5 identifier or <npub> to send the confirmation code to.',
|
||||||
|
|
||||||
new_user_not_allowed: 'Registration is disabled.',
|
new_user_not_allowed: 'Registration is disabled.',
|
||||||
start_user_impersonation: 'Impersonate this user',
|
start_user_impersonation: 'Impersonate this user',
|
||||||
stop_user_impersonation: 'Stop User Impersonation',
|
stop_user_impersonation: 'Stop User Impersonation',
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ window._lnbitsApi = {
|
|||||||
name: name
|
name: name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
register(username, email, password, password_repeat) {
|
register(username, email, password, password_repeat, invitation_code) {
|
||||||
return axios({
|
return axios({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/v1/auth/register',
|
url: '/api/v1/auth/register',
|
||||||
@@ -74,7 +74,8 @@ window._lnbitsApi = {
|
|||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
password_repeat
|
password_repeat,
|
||||||
|
invitation_code
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -147,13 +148,16 @@ window._lnbitsApi = {
|
|||||||
name: name,
|
name: name,
|
||||||
wallet_type: walletType,
|
wallet_type: walletType,
|
||||||
...opts
|
...opts
|
||||||
}).catch(LNbits.utils.notifyApiError)
|
})
|
||||||
},
|
},
|
||||||
updateWallet(name, wallet) {
|
updateWallet(name, wallet) {
|
||||||
return this.request('patch', '/api/v1/wallet', wallet.adminkey, {
|
return this.request('patch', '/api/v1/wallet', wallet.adminkey, {
|
||||||
name: name
|
name: name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
updateUiCustomization(data = {}) {
|
||||||
|
return this.request('patch', '/api/v1/auth/ui', null, data)
|
||||||
|
},
|
||||||
resetWalletKeys(wallet) {
|
resetWalletKeys(wallet) {
|
||||||
return this.request('put', `/api/v1/wallet/reset/${wallet.id}`).then(
|
return this.request('put', `/api/v1/wallet/reset/${wallet.id}`).then(
|
||||||
res => {
|
res => {
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ window.LNbits = {
|
|||||||
fiat_providers: data.fiat_providers || [],
|
fiat_providers: data.fiat_providers || [],
|
||||||
super_user: data.super_user,
|
super_user: data.super_user,
|
||||||
extra: data.extra ?? {},
|
extra: data.extra ?? {},
|
||||||
hasPassword: data.has_password ?? false
|
hasPassword: data.has_password ?? false,
|
||||||
|
uiCustomization: data.ui_customization || {}
|
||||||
}
|
}
|
||||||
const mapWallet = this.wallet
|
const mapWallet = this.wallet
|
||||||
obj.wallets = obj.wallets.map(mapWallet).sort((a, b) => {
|
obj.wallets = obj.wallets.map(mapWallet).sort((a, b) => {
|
||||||
|
|||||||
@@ -35,23 +35,6 @@ window.app.component('lnbits-extension-rating', {
|
|||||||
|
|
||||||
window.app.component('lnbits-manage', {
|
window.app.component('lnbits-manage', {
|
||||||
template: '#lnbits-manage',
|
template: '#lnbits-manage',
|
||||||
computed: {
|
|
||||||
showAdmin() {
|
|
||||||
return this.LNBITS_ADMIN_UI
|
|
||||||
},
|
|
||||||
showUsers() {
|
|
||||||
return this.LNBITS_ADMIN_UI
|
|
||||||
},
|
|
||||||
showNode() {
|
|
||||||
return this.LNBITS_NODE_UI
|
|
||||||
},
|
|
||||||
showAudit() {
|
|
||||||
return this.LNBITS_AUDIT_ENABLED
|
|
||||||
},
|
|
||||||
showExtensions() {
|
|
||||||
return this.LNBITS_EXTENSIONS_DEACTIVATE_ALL === false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
isActive(path) {
|
isActive(path) {
|
||||||
return window.location.pathname === path
|
return window.location.pathname === path
|
||||||
@@ -449,8 +432,10 @@ window.app.component('username-password', {
|
|||||||
username: String,
|
username: String,
|
||||||
password_1: String,
|
password_1: String,
|
||||||
password_2: String,
|
password_2: String,
|
||||||
|
invitationCode: String,
|
||||||
resetKey: String
|
resetKey: String
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
oauth: [
|
oauth: [
|
||||||
@@ -463,8 +448,10 @@ window.app.component('username-password', {
|
|||||||
password: this.password_1,
|
password: this.password_1,
|
||||||
passwordRepeat: this.password_2,
|
passwordRepeat: this.password_2,
|
||||||
reset_key: this.resetKey,
|
reset_key: this.resetKey,
|
||||||
keycloakOrg: LNBITS_AUTH_KEYCLOAK_ORG || 'Keycloak',
|
confirmationMethod: 'code',
|
||||||
keycloakIcon: LNBITS_AUTH_KEYCLOAK_ICON
|
confirmationEmail: '',
|
||||||
|
confirmationCode: this.invitationCode || '',
|
||||||
|
showConfirmationCode: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -477,6 +464,7 @@ window.app.component('username-password', {
|
|||||||
this.$emit('update:userName', this.username)
|
this.$emit('update:userName', this.username)
|
||||||
this.$emit('update:password_1', this.password)
|
this.$emit('update:password_1', this.password)
|
||||||
this.$emit('update:password_2', this.passwordRepeat)
|
this.$emit('update:password_2', this.passwordRepeat)
|
||||||
|
this.$emit('update:invitationCode', this.confirmationCode)
|
||||||
this.$emit('register')
|
this.$emit('register')
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
@@ -568,6 +556,25 @@ window.app.component('username-password', {
|
|||||||
computed: {
|
computed: {
|
||||||
showOauth() {
|
showOauth() {
|
||||||
return this.oauth.some(m => this.authMethods.includes(m))
|
return this.oauth.some(m => this.authMethods.includes(m))
|
||||||
|
},
|
||||||
|
disableRegister() {
|
||||||
|
const usernameOK = !!this.username
|
||||||
|
const passwordOK = !!this.password && this.password.length >= 8
|
||||||
|
const passwordsMatch = this.password === this.passwordRepeat
|
||||||
|
const codeOk =
|
||||||
|
this.confirmationMethodsCount === 0 ||
|
||||||
|
this.confirmationMethod !== 'code' ||
|
||||||
|
this.confirmationCode.length > 0
|
||||||
|
|
||||||
|
return !usernameOK || !passwordOK || !passwordsMatch || !codeOk
|
||||||
|
},
|
||||||
|
confirmationMethodsCount() {
|
||||||
|
const methods = [
|
||||||
|
this.g.settings.userActivationByEmail,
|
||||||
|
this.g.settings.userActivationByPayment,
|
||||||
|
this.g.settings.userActivationByInvitationCode
|
||||||
|
]
|
||||||
|
return methods.filter(Boolean).length
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {}
|
created() {}
|
||||||
|
|||||||
@@ -229,46 +229,43 @@ window.app.component('lnbits-admin-funding-sources', {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'LightsparkSparkWallet',
|
'SparkL2Wallet',
|
||||||
'Spark (L2)',
|
'Spark (L2)',
|
||||||
{
|
{
|
||||||
spark_l2_internal_sidecar_version: {
|
spark_l2_external_endpoint: {
|
||||||
label: 'Internal Sidecar Version (eg: 0.1.1).',
|
label: 'External Sidecar Endpoint',
|
||||||
hint: 'If specified then the sidecar will be downloaded. Alternatively you can specify an External Sidecar endpoint in the Advanced section.',
|
hint: 'Make sure to also specify the API key if your sidecar requires authentication.',
|
||||||
value: ''
|
value: ''
|
||||||
},
|
},
|
||||||
spark_l2_mnemonic: {
|
spark_l2_mnemonic: {
|
||||||
label: 'Mnemonic',
|
label: 'External Sidecar Mnemonic',
|
||||||
hint: 'Only required if Interna Sidecar version is specified.'
|
hint: 'Mnemonic for the Spark wallet on the external sidecar. Required if the side car does not have its own mnemonic.',
|
||||||
|
value: ''
|
||||||
|
},
|
||||||
|
spark_l2_external_api_key: {
|
||||||
|
label: 'External Sidecar API Key',
|
||||||
|
hint: 'API key for authenticating with the external sidecar if it requires authentication.',
|
||||||
|
value: ''
|
||||||
},
|
},
|
||||||
spark_l2_network: {
|
spark_l2_network: {
|
||||||
label: 'Network',
|
label: 'Network',
|
||||||
value: 'MAINNET'
|
value: 'MAINNET',
|
||||||
},
|
hint: 'The network to use for the Spark wallet.',
|
||||||
|
|
||||||
spark_l2_external_endpoint: {
|
|
||||||
label: 'External Sidecar Endpoint. ',
|
|
||||||
hint: 'If specified then this endpoint will be used instead of the internal sidecar. Make sure to also specify the API key if your sidecar requires authentication.',
|
|
||||||
value: '',
|
|
||||||
advanced: true
|
advanced: true
|
||||||
},
|
},
|
||||||
spark_l2_external_api_key: {
|
|
||||||
label: 'External Sidecar API Key. ',
|
|
||||||
hint: 'API Key for authenticating with the external sidecar if it requires authentication.',
|
|
||||||
value: '',
|
|
||||||
advanced: true
|
|
||||||
},
|
|
||||||
|
|
||||||
spark_l2_pay_wait_ms: {
|
spark_l2_pay_wait_ms: {
|
||||||
label: 'Pay Wait Time (ms)',
|
label: 'Payment Wait Time (ms)',
|
||||||
|
hint: 'The time to wait for a payment to be processed before considering it failed.',
|
||||||
advanced: true
|
advanced: true
|
||||||
},
|
},
|
||||||
spark_l2_pay_poll_ms: {
|
spark_l2_pay_poll_ms: {
|
||||||
label: 'Pay Poll Time (ms)',
|
label: 'Payment Poll Time (ms)',
|
||||||
|
hint: 'The time to wait between polling for payment status updates.',
|
||||||
advanced: true
|
advanced: true
|
||||||
},
|
},
|
||||||
spark_l2_stream_keepalive_ms: {
|
spark_l2_stream_keepalive_ms: {
|
||||||
label: 'Stream Keepalive Time (ms)',
|
label: 'Stream Keepalive Time (ms)',
|
||||||
|
hint: 'The time to wait between sending keepalive messages to the Spark sidecar to keep the connection open.',
|
||||||
advanced: true
|
advanced: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ window.app.component('lnbits-admin-users', {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formAddUser: '',
|
formAddUser: '',
|
||||||
formAddAdmin: ''
|
formAddAdmin: '',
|
||||||
|
formAddActivationCode: '',
|
||||||
|
showReusableActivationCode: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -31,6 +33,24 @@ window.app.component('lnbits-admin-users', {
|
|||||||
removeAdminUser(user) {
|
removeAdminUser(user) {
|
||||||
let admin_users = this.formData.lnbits_admin_users
|
let admin_users = this.formData.lnbits_admin_users
|
||||||
this.formData.lnbits_admin_users = admin_users.filter(u => u !== user)
|
this.formData.lnbits_admin_users = admin_users.filter(u => u !== user)
|
||||||
|
},
|
||||||
|
addOneTimeActivationCode() {
|
||||||
|
const code = this.formAddActivationCode
|
||||||
|
const activationCodes =
|
||||||
|
this.formData.lnbits_register_one_time_activation_codes
|
||||||
|
if (code?.length && !activationCodes.includes(code)) {
|
||||||
|
this.formData.lnbits_register_one_time_activation_codes = [
|
||||||
|
...activationCodes,
|
||||||
|
code
|
||||||
|
]
|
||||||
|
this.formAddActivationCode = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeOneTimeActivationCode(code) {
|
||||||
|
const codes = this.formData.lnbits_register_one_time_activation_codes
|
||||||
|
this.formData.lnbits_register_one_time_activation_codes = codes.filter(
|
||||||
|
u => u !== code
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
window.app.component('lnbits-footer', {
|
window.app.component('lnbits-footer', {
|
||||||
template: '#lnbits-footer',
|
template: '#lnbits-footer',
|
||||||
computed: {
|
computed: {
|
||||||
version() {
|
|
||||||
return this.LNBITS_VERSION || 'unknown version'
|
|
||||||
},
|
|
||||||
title() {
|
title() {
|
||||||
return `${this.SITE_TITLE}, ${this.SITE_TAGLINE}`
|
return `${this.g.settings.siteTitle}, ${this.g.settings.siteTagline}`
|
||||||
},
|
},
|
||||||
showFooter() {
|
version() {
|
||||||
return this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
return this.$t('lnbits_version') + ': ' + this.g.settings.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,48 +1,9 @@
|
|||||||
window.app.component('lnbits-header', {
|
window.app.component('lnbits-header', {
|
||||||
template: '#lnbits-header',
|
template: '#lnbits-header',
|
||||||
computed: {
|
computed: {
|
||||||
hasServiceFeeMax() {
|
|
||||||
return (
|
|
||||||
this.g.user &&
|
|
||||||
this.LNBITS_SERVICE_FEE_MAX &&
|
|
||||||
this.LNBITS_SERVICE_FEE_MAX > 0
|
|
||||||
)
|
|
||||||
},
|
|
||||||
serviceFeeMax() {
|
|
||||||
return this.LNBITS_SERVICE_FEE_MAX || 0
|
|
||||||
},
|
|
||||||
hasServiceFee() {
|
|
||||||
return (
|
|
||||||
this.g.user && this.LNBITS_SERVICE_FEE && this.LNBITS_SERVICE_FEE > 0
|
|
||||||
)
|
|
||||||
},
|
|
||||||
serviceFee() {
|
|
||||||
return this.LNBITS_SERVICE_FEE || 0
|
|
||||||
},
|
|
||||||
hasCustomBadge() {
|
|
||||||
return this.LNBITS_CUSTOM_BADGE && this.LNBITS_CUSTOM_BADGE != ''
|
|
||||||
},
|
|
||||||
customBadge() {
|
|
||||||
return this.LNBITS_CUSTOM_BADGE || ''
|
|
||||||
},
|
|
||||||
customBadgeColor() {
|
|
||||||
return this.LNBITS_CUSTOM_BADGE_COLOR || ''
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
return this.SITE_TITLE
|
|
||||||
},
|
|
||||||
titleIsLnbits() {
|
|
||||||
return this.SITE_TITLE == 'LNbits'
|
|
||||||
},
|
|
||||||
customLogoUrl() {
|
|
||||||
return this.USE_CUSTOM_LOGO || null
|
|
||||||
},
|
|
||||||
showAdmin() {
|
showAdmin() {
|
||||||
return this.g.user && (this.g.user.super_user || this.g.user.admin)
|
return this.g.user && (this.g.user.super_user || this.g.user.admin)
|
||||||
},
|
},
|
||||||
showVoidwallet() {
|
|
||||||
return this.g.user && this.VOIDWALLET == true
|
|
||||||
},
|
|
||||||
displayName() {
|
displayName() {
|
||||||
return (
|
return (
|
||||||
this.g.user?.extra?.display_name ||
|
this.g.user?.extra?.display_name ||
|
||||||
@@ -69,6 +30,17 @@ window.app.component('lnbits-header', {
|
|||||||
window.location = '/users'
|
window.location = '/users'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e)
|
console.warn(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async handleLanguageChanged(lang) {
|
||||||
|
try {
|
||||||
|
await LNbits.api.updateUiCustomization({locale: lang.locale})
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Language Updated',
|
||||||
|
caption: lang.locale
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
LNbits.utils.notifyApiError(e)
|
LNbits.utils.notifyApiError(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,8 +95,8 @@ window.app.component('lnbits-home-logos', {
|
|||||||
showLogos() {
|
showLogos() {
|
||||||
return (
|
return (
|
||||||
this.g.isSatsDenomination &&
|
this.g.isSatsDenomination &&
|
||||||
this.SITE_TITLE == 'LNbits' &&
|
this.g.settings.siteTitle == 'LNbits' &&
|
||||||
this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
this.g.settings.showHomePageElements == true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,16 @@
|
|||||||
window.app.component('lnbits-language-dropdown', {
|
window.app.component('lnbits-language-dropdown', {
|
||||||
template: '#lnbits-language-dropdown',
|
template: '#lnbits-language-dropdown',
|
||||||
|
computed: {
|
||||||
|
currentLanguage() {
|
||||||
|
return (
|
||||||
|
this.langs.find(lang => lang.value === window.i18n.global.locale) || {
|
||||||
|
value: 'en',
|
||||||
|
label: 'English',
|
||||||
|
display: '🇬🇧 EN'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
activeLanguage(lang) {
|
activeLanguage(lang) {
|
||||||
return window.i18n.global.locale === lang
|
return window.i18n.global.locale === lang
|
||||||
@@ -8,6 +19,7 @@ window.app.component('lnbits-language-dropdown', {
|
|||||||
this.g.locale = newValue
|
this.g.locale = newValue
|
||||||
window.i18n.global.locale = newValue
|
window.i18n.global.locale = newValue
|
||||||
this.$q.localStorage.set('lnbits.lang', newValue)
|
this.$q.localStorage.set('lnbits.lang', newValue)
|
||||||
|
this.$emit('language-changed', newValue)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ window.app.component('lnbits-manage-wallet-list', {
|
|||||||
} else {
|
} else {
|
||||||
this.activeWalletId = null
|
this.activeWalletId = null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'g.user.wallets': {
|
||||||
|
handler() {
|
||||||
|
this.paymentEvents()
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
immediate: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ window.app.component('lnbits-qrcode', {
|
|||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
type: String,
|
type: String,
|
||||||
default: LNBITS_QR_LOGO
|
default: window.g.settings.qrLogo || null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ window.app.component('lnbits-theme', {
|
|||||||
'g.disclaimerShown'(val) {
|
'g.disclaimerShown'(val) {
|
||||||
this.$q.localStorage.setItem('lnbits.disclaimerShown', val)
|
this.$q.localStorage.setItem('lnbits.disclaimerShown', val)
|
||||||
},
|
},
|
||||||
|
'g.locale'(val) {
|
||||||
|
this.$q.localStorage.setItem('lnbits.lang', val)
|
||||||
|
window.i18n.global.locale = val
|
||||||
|
},
|
||||||
'g.isFiatPriority'(val) {
|
'g.isFiatPriority'(val) {
|
||||||
this.$q.localStorage.setItem('lnbits.isFiatPriority', val)
|
this.$q.localStorage.setItem('lnbits.isFiatPriority', val)
|
||||||
},
|
},
|
||||||
@@ -41,6 +45,30 @@ window.app.component('lnbits-theme', {
|
|||||||
document.body.classList.remove('gradient-bg')
|
document.body.classList.remove('gradient-bg')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'g.cardRoundedChoice'(val) {
|
||||||
|
this.$q.localStorage.set('lnbits.cardRounded', val)
|
||||||
|
if (val === true) {
|
||||||
|
document.body.classList.add('rounded-ui')
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('rounded-ui')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'g.cardGradientChoice'(val) {
|
||||||
|
this.$q.localStorage.set('lnbits.cardGradient', val)
|
||||||
|
if (val === true) {
|
||||||
|
document.body.classList.add('card-gradient')
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('card-gradient')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'g.cardShadowChoice'(val) {
|
||||||
|
this.$q.localStorage.set('lnbits.cardShadow', val)
|
||||||
|
if (val === true) {
|
||||||
|
document.body.classList.add('card-shadow')
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('card-shadow')
|
||||||
|
}
|
||||||
|
},
|
||||||
'g.mobileSimple'(val) {
|
'g.mobileSimple'(val) {
|
||||||
this.$q.localStorage.set('lnbits.mobileSimple', val)
|
this.$q.localStorage.set('lnbits.mobileSimple', val)
|
||||||
if (val === true) {
|
if (val === true) {
|
||||||
@@ -113,6 +141,15 @@ window.app.component('lnbits-theme', {
|
|||||||
if (this.g.gradientChoice === true) {
|
if (this.g.gradientChoice === true) {
|
||||||
document.body.classList.add('gradient-bg')
|
document.body.classList.add('gradient-bg')
|
||||||
}
|
}
|
||||||
|
if (this.g.cardRoundedChoice === true) {
|
||||||
|
document.body.classList.add('rounded-ui')
|
||||||
|
}
|
||||||
|
if (this.g.cardGradientChoice === true) {
|
||||||
|
document.body.classList.add('card-gradient')
|
||||||
|
}
|
||||||
|
if (this.g.cardShadowChoice === true) {
|
||||||
|
document.body.classList.add('card-shadow')
|
||||||
|
}
|
||||||
if (this.g.bgimageChoice !== '') {
|
if (this.g.bgimageChoice !== '') {
|
||||||
document.body.classList.add('bg-image')
|
document.body.classList.add('bg-image')
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
@@ -123,6 +160,13 @@ window.app.component('lnbits-theme', {
|
|||||||
if (this.g.mobileSimple === true) {
|
if (this.g.mobileSimple === true) {
|
||||||
document.body.classList.add('mobile-simple')
|
document.body.classList.add('mobile-simple')
|
||||||
}
|
}
|
||||||
|
Object.entries(this.g.user?.uiCustomization || {}).forEach(
|
||||||
|
([key, value]) => {
|
||||||
|
if (key in this.g) {
|
||||||
|
this.g[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
this.checkUrlParams()
|
this.checkUrlParams()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ window.app.component('lnbits-wallet-extra', {
|
|||||||
'lnbits.exchangeRate.' + this.g.wallet.currency,
|
'lnbits.exchangeRate.' + this.g.wallet.currency,
|
||||||
this.g.exchangeRate
|
this.g.exchangeRate
|
||||||
)
|
)
|
||||||
|
if (this.g.exchangeRate <= 0) {
|
||||||
|
this.g.fiatTracking = false
|
||||||
|
this.g.isFiatPriority = false
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(e => console.error(e))
|
.catch(e => console.error(e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ window.app.component('lnbits-wallet-new', {
|
|||||||
this.g.lastWalletId = res.data.id
|
this.g.lastWalletId = res.data.id
|
||||||
this.$router.push(`/wallet/${res.data.id}`)
|
this.$router.push(`/wallet/${res.data.id}`)
|
||||||
})
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|||||||
+36
-32
@@ -9,17 +9,50 @@ const localStore = (key, defaultValue) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
window.g = Vue.reactive({
|
window.g = Vue.reactive({
|
||||||
|
// vars from server
|
||||||
|
settings: SETTINGS,
|
||||||
|
currencies: CURRENCIES,
|
||||||
|
extensions: SETTINGS.extensions,
|
||||||
|
allowedCurrencies: SETTINGS.allowedCurrencies,
|
||||||
|
denomination: SETTINGS.denomination,
|
||||||
|
isSatsDenomination: SETTINGS.denomination == 'sats',
|
||||||
|
// local storage vars
|
||||||
|
themeChoice: localStore('lnbits.theme', SETTINGS.defaultTheme),
|
||||||
|
borderChoice: localStore('lnbits.border', SETTINGS.defaultBorder),
|
||||||
|
gradientChoice: localStore('lnbits.gradientBg', SETTINGS.defaultGradient),
|
||||||
|
cardRoundedChoice: localStore(
|
||||||
|
'lnbits.cardRounded',
|
||||||
|
SETTINGS.defaultCardRounded
|
||||||
|
),
|
||||||
|
cardGradientChoice: localStore(
|
||||||
|
'lnbits.cardGradient',
|
||||||
|
SETTINGS.defaultCardGradient
|
||||||
|
),
|
||||||
|
cardShadowChoice: localStore('lnbits.cardShadow', SETTINGS.defaultCardShadow),
|
||||||
|
reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction),
|
||||||
|
bgimageChoice: localStore(
|
||||||
|
'lnbits.backgroundImage',
|
||||||
|
SETTINGS.defaultBgimage || ''
|
||||||
|
),
|
||||||
|
locale: localStore('lnbits.lang', navigator.languages[1] ?? 'en'),
|
||||||
|
disclaimerShown: localStore('lnbits.disclaimerShown', false),
|
||||||
|
isFiatPriority: localStore('lnbits.isFiatPriority', false),
|
||||||
|
mobileSimple: localStore('lnbits.mobileSimple', true),
|
||||||
|
walletFlip: localStore('lnbits.walletFlip', false),
|
||||||
|
lastActiveWallet: localStore('lnbits.lastActiveWallet', null),
|
||||||
|
darkChoice: localStore('lnbits.darkMode', SETTINGS.defaultDark),
|
||||||
|
// cookie vars
|
||||||
|
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
||||||
|
isUserImpersonated: !!Quasar.Cookies.get('is_lnbits_user_impersonated'),
|
||||||
|
// frontend vars
|
||||||
errorCode: null,
|
errorCode: null,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
user: null,
|
user: null,
|
||||||
wallet: null,
|
wallet: null,
|
||||||
isPublicPage: true,
|
isPublicPage: true,
|
||||||
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
|
||||||
isUserImpersonated: !!Quasar.Cookies.get('is_lnbits_user_impersonated'),
|
|
||||||
offline: !navigator.onLine,
|
offline: !navigator.onLine,
|
||||||
hasCamera: false,
|
hasCamera: false,
|
||||||
visibleDrawer: false,
|
visibleDrawer: false,
|
||||||
extensions: WINDOW_SETTINGS.EXTENSIONS,
|
|
||||||
fiatBalance: 0,
|
fiatBalance: 0,
|
||||||
exchangeRate: 0,
|
exchangeRate: 0,
|
||||||
fiatTracking: false,
|
fiatTracking: false,
|
||||||
@@ -27,35 +60,6 @@ window.g = Vue.reactive({
|
|||||||
walletEventListeners: [],
|
walletEventListeners: [],
|
||||||
updatePayments: false, // used for updating the lnbits-payment-list
|
updatePayments: false, // used for updating the lnbits-payment-list
|
||||||
updatePaymentsHash: false, // used for closing the receive dialog
|
updatePaymentsHash: false, // used for closing the receive dialog
|
||||||
currencies: WINDOW_SETTINGS.LNBITS_CURRENCIES ?? [],
|
|
||||||
allowedCurrencies: WINDOW_SETTINGS.LNBITS_ALLOWED_CURRENCIES ?? [],
|
|
||||||
locale: localStore('lnbits.lang', navigator.languages[1] ?? 'en'),
|
|
||||||
disclaimerShown: localStore('lnbits.disclaimerShown', false),
|
|
||||||
isFiatPriority: localStore('lnbits.isFiatPriority', false),
|
|
||||||
mobileSimple: localStore('lnbits.mobileSimple', true),
|
|
||||||
walletFlip: localStore('lnbits.walletFlip', false),
|
|
||||||
lastActiveWallet: localStore('lnbits.lastActiveWallet', null),
|
|
||||||
darkChoice: localStore('lnbits.darkMode', true),
|
|
||||||
themeChoice: localStore('lnbits.theme', WINDOW_SETTINGS.LNBITS_DEFAULT_THEME),
|
|
||||||
borderChoice: localStore(
|
|
||||||
'lnbits.border',
|
|
||||||
WINDOW_SETTINGS.LNBITS_DEFAULT_BORDER || 'hard-border'
|
|
||||||
),
|
|
||||||
gradientChoice: localStore(
|
|
||||||
'lnbits.gradientBg',
|
|
||||||
WINDOW_SETTINGS.LNBITS_DEFAULT_GRADIENT || false
|
|
||||||
),
|
|
||||||
reactionChoice: localStore(
|
|
||||||
'lnbits.reactions',
|
|
||||||
WINDOW_SETTINGS.LNBITS_DEFAULT_REACTION || 'confettiBothSides'
|
|
||||||
),
|
|
||||||
bgimageChoice: localStore(
|
|
||||||
'lnbits.backgroundImage',
|
|
||||||
WINDOW_SETTINGS.LNBITS_DEFAULT_BGIMAGE || ''
|
|
||||||
),
|
|
||||||
ads: WINDOW_SETTINGS.AD_SPACE.split(',').map(ad => ad.split(';')),
|
|
||||||
denomination: WINDOW_SETTINGS.LNBITS_DENOMINATION,
|
|
||||||
isSatsDenomination: WINDOW_SETTINGS.LNBITS_DENOMINATION == 'sats',
|
|
||||||
scanner: null,
|
scanner: null,
|
||||||
newWalletType: null
|
newWalletType: null
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -157,8 +157,7 @@ window.app.mixin({
|
|||||||
return {
|
return {
|
||||||
api: window._lnbitsApi,
|
api: window._lnbitsApi,
|
||||||
utils: window._lnbitsUtils,
|
utils: window._lnbitsUtils,
|
||||||
g: window.g,
|
g: window.g
|
||||||
...WINDOW_SETTINGS
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// backwards compatibility for extensions, should not be used in the future
|
// backwards compatibility for extensions, should not be used in the future
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ window.PageAccount = {
|
|||||||
color: 'pink-3'
|
color: 'pink-3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
defaultSiteCustomisation: {
|
||||||
|
locale: 'en'
|
||||||
|
},
|
||||||
reactionOptions: [
|
reactionOptions: [
|
||||||
'None',
|
'None',
|
||||||
'confettiBothSides',
|
'confettiBothSides',
|
||||||
@@ -213,26 +216,18 @@ window.PageAccount = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
activeLanguage(lang) {
|
|
||||||
return window.i18n.global.locale === lang
|
|
||||||
},
|
|
||||||
changeLanguage(newValue) {
|
changeLanguage(newValue) {
|
||||||
window.i18n.global.locale = newValue
|
window.i18n.global.locale = newValue
|
||||||
this.$q.localStorage.set('lnbits.lang', newValue)
|
this.$q.localStorage.set('lnbits.lang', newValue)
|
||||||
},
|
},
|
||||||
async updateAccount() {
|
async updateAccount() {
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request('PATCH', '/api/v1/auth', null, {
|
||||||
'PUT',
|
user_id: this.g.user.id,
|
||||||
'/api/v1/auth/update',
|
username: this.g.user.username,
|
||||||
null,
|
email: this.g.user.email,
|
||||||
{
|
extra: this.g.user.extra
|
||||||
user_id: this.g.user.id,
|
})
|
||||||
username: this.g.user.username,
|
|
||||||
email: this.g.user.email,
|
|
||||||
extra: this.g.user.extra
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.untouchedUser = JSON.parse(JSON.stringify(this.g.user))
|
this.untouchedUser = JSON.parse(JSON.stringify(this.g.user))
|
||||||
this.hasUsername = !!data.username
|
this.hasUsername = !!data.username
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
@@ -680,6 +675,37 @@ window.PageAccount = {
|
|||||||
l => l.name !== label.name
|
l => l.name !== label.name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
async siteCustomisationChanged(options = {}) {
|
||||||
|
try {
|
||||||
|
Object.entries(options || {}).forEach(([key, value]) => {
|
||||||
|
if (key in this.g) {
|
||||||
|
this.g[key] = value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await LNbits.api.updateUiCustomization(options)
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'UI Customization updated.'
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
LNbits.utils.notifyApiError(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetThemeDefaults() {
|
||||||
|
const defaults = {
|
||||||
|
themeChoice: this.g.settings.defaultTheme,
|
||||||
|
borderChoice: this.g.settings.defaultBorder,
|
||||||
|
gradientChoice: this.g.settings.defaultGradient,
|
||||||
|
bgimageChoice: this.g.settings.defaultBgimage || '',
|
||||||
|
reactionChoice: this.g.settings.defaultReaction,
|
||||||
|
darkChoice: this.g.settings.defaultDark,
|
||||||
|
cardRoundedChoice: this.g.settings.defaultCardRounded,
|
||||||
|
cardGradientChoice: this.g.settings.defaultCardGradient,
|
||||||
|
cardShadowChoice: this.g.settings.defaultCardShadow
|
||||||
|
}
|
||||||
|
this.siteCustomisationChanged(defaults)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -693,7 +719,7 @@ window.PageAccount = {
|
|||||||
await this.getUserAssets()
|
await this.getUserAssets()
|
||||||
// filter out themes that are not allowed
|
// filter out themes that are not allowed
|
||||||
this.themeOptions = this.themeOptions.filter(theme =>
|
this.themeOptions = this.themeOptions.filter(theme =>
|
||||||
this.LNBITS_THEME_OPTIONS.includes(theme.name)
|
this.g.settings.themeOptions.includes(theme.name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -842,8 +842,8 @@ window.PageExtensions = {
|
|||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.extensions = await this.fetchAllExtensions()
|
this.extensions = await this.fetchAllExtensions()
|
||||||
this.extbuilderEnabled = this.g.user.admin || this.LNBITS_EXT_BUILDER
|
this.extbuilderEnabled = this.g.user.admin || this.g.settings.extBuilder
|
||||||
this.reviewsUrl = this.LNBITS_EXTENSIONS_REVIEWS_URL
|
this.reviewsUrl = this.g.settings.extensionsReviewsUrl
|
||||||
|
|
||||||
if (this.g.user.extensions.length === 0) {
|
if (this.g.user.extensions.length === 0) {
|
||||||
this.tab = 'all'
|
this.tab = 'all'
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ window.PageFirstInstall = {
|
|||||||
isPwdRepeat: true,
|
isPwdRepeat: true,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordRepeat: ''
|
passwordRepeat: '',
|
||||||
|
firstInstallToken: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -17,20 +18,25 @@ window.PageFirstInstall = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async setPassword() {
|
setPassword() {
|
||||||
try {
|
LNbits.api
|
||||||
await LNbits.api.request('PUT', '/api/v1/auth/first_install', null, {
|
.request('PUT', `/api/v1/auth/first_install`, null, {
|
||||||
username: this.loginData.username,
|
username: this.loginData.username,
|
||||||
password: this.loginData.password,
|
password: this.loginData.password,
|
||||||
password_repeat: this.loginData.passwordRepeat
|
password_repeat: this.loginData.passwordRepeat,
|
||||||
|
first_install_token: this.loginData.firstInstallToken
|
||||||
})
|
})
|
||||||
window.location.href = '/admin'
|
.then(async () => {
|
||||||
} catch (e) {
|
const res = await LNbits.api.getAuthUser()
|
||||||
LNbits.utils.notifyApiError(e)
|
this.g.user = LNbits.map.user(res.data)
|
||||||
}
|
this.g.isPublicPage = false
|
||||||
|
this.$router.push('/admin')
|
||||||
|
})
|
||||||
|
.catch(this.utils.notifyApiError)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
document.title = 'First Install - LNbits'
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
this.loginData.firstInstallToken = params.get('token') || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ window.PageHome = {
|
|||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordRepeat: '',
|
passwordRepeat: '',
|
||||||
|
invitationCode: '',
|
||||||
walletName: '',
|
walletName: '',
|
||||||
signup: false
|
signup: false
|
||||||
}
|
}
|
||||||
@@ -19,40 +20,15 @@ window.PageHome = {
|
|||||||
showClaimLnurl() {
|
showClaimLnurl() {
|
||||||
return (
|
return (
|
||||||
this.lnurl !== '' &&
|
this.lnurl !== '' &&
|
||||||
this.allowRegister &&
|
this.g.settings.allowRegister &&
|
||||||
'user-id-only' in this.LNBITS_AUTH_METHODS
|
'user-id-only' in this.g.settings.authMethods
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
formatDescription() {
|
formatDescription() {
|
||||||
return LNbits.utils.convertMarkdown(this.SITE_DESCRIPTION)
|
return LNbits.utils.convertMarkdown(this.g.settings.siteDescription)
|
||||||
},
|
},
|
||||||
isAccessTokenExpired() {
|
isAccessTokenExpired() {
|
||||||
return this.$q.cookies.get('is_access_token_expired')
|
return this.$q.cookies.get('is_access_token_expired')
|
||||||
},
|
|
||||||
allowRegister() {
|
|
||||||
return this.LNBITS_NEW_ACCOUNTS_ALLOWED
|
|
||||||
},
|
|
||||||
// TODO: this makes no sense
|
|
||||||
hasCustomImage() {
|
|
||||||
return this.LNBITS_CUSTOM_IMAGE
|
|
||||||
},
|
|
||||||
showHomepageElements() {
|
|
||||||
return this.LNBITS_SHOW_HOME_PAGE_ELEMENTS === true
|
|
||||||
},
|
|
||||||
siteTitle() {
|
|
||||||
return this.SITE_TITLE || ''
|
|
||||||
},
|
|
||||||
siteTagline() {
|
|
||||||
return this.SITE_TAGLINE || ''
|
|
||||||
},
|
|
||||||
adsEnabled() {
|
|
||||||
return this.AD_SPACE_ENABLED && this.AD_SPACE && this.AD_SPACE.length > 0
|
|
||||||
},
|
|
||||||
adsTitle() {
|
|
||||||
return this.AD_SPACE_TITLE || ''
|
|
||||||
},
|
|
||||||
ads() {
|
|
||||||
return this.AD_SPACE.map(ad => ad.split(';'))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -65,6 +41,7 @@ window.PageHome = {
|
|||||||
this.username = null
|
this.username = null
|
||||||
this.password = null
|
this.password = null
|
||||||
this.passwordRepeat = null
|
this.passwordRepeat = null
|
||||||
|
this.invitationCode = null
|
||||||
|
|
||||||
this.authAction = 'register'
|
this.authAction = 'register'
|
||||||
this.authMethod = authMethod
|
this.authMethod = authMethod
|
||||||
@@ -76,7 +53,8 @@ window.PageHome = {
|
|||||||
this.username,
|
this.username,
|
||||||
this.email,
|
this.email,
|
||||||
this.password,
|
this.password,
|
||||||
this.passwordRepeat
|
this.passwordRepeat,
|
||||||
|
this.invitationCode
|
||||||
)
|
)
|
||||||
this.refreshAuthUser()
|
this.refreshAuthUser()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ window.PageUsers = {
|
|||||||
usersTable: {
|
usersTable: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'admin',
|
name: 'activated',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Admin',
|
label: this.$t('activated'),
|
||||||
field: 'admin',
|
field: 'activated',
|
||||||
sortable: false
|
sortable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -401,7 +401,7 @@ window.PageUsers = {
|
|||||||
|
|
||||||
toggleAdmin(userId) {
|
toggleAdmin(userId) {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('GET', `/users/api/v1/user/${userId}/admin`)
|
.request('PUT', `/users/api/v1/user/${userId}/admin`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.fetchUsers()
|
this.fetchUsers()
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
@@ -412,6 +412,19 @@ window.PageUsers = {
|
|||||||
})
|
})
|
||||||
.catch(LNbits.utils.notifyApiError)
|
.catch(LNbits.utils.notifyApiError)
|
||||||
},
|
},
|
||||||
|
toggleUserActivated(userId) {
|
||||||
|
LNbits.api
|
||||||
|
.request('PUT', `/users/api/v1/user/${userId}/activate`)
|
||||||
|
.then(res => {
|
||||||
|
this.fetchUsers()
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: res.data.message,
|
||||||
|
icon: null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
async showAccountPage(user_id) {
|
async showAccountPage(user_id) {
|
||||||
this.activeUser.showPassword = false
|
this.activeUser.showPassword = false
|
||||||
this.activeUser.showUserId = false
|
this.activeUser.showUserId = false
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
window._lnbitsUtils = {
|
window._lnbitsUtils = {
|
||||||
url_for(url) {
|
url_for(url) {
|
||||||
const _url = new URL(url, window.location.origin)
|
const _url = new URL(url, window.location.origin)
|
||||||
_url.searchParams.set('v', WINDOW_SETTINGS.CACHE_KEY)
|
_url.searchParams.set('v', window.g.settings.cacheKey)
|
||||||
return _url.toString()
|
return _url.toString()
|
||||||
},
|
},
|
||||||
loadScript(src) {
|
loadScript(src) {
|
||||||
@@ -131,6 +131,22 @@ window._lnbitsUtils = {
|
|||||||
currency: currency || 'sat'
|
currency: currency || 'sat'
|
||||||
}).format(value)
|
}).format(value)
|
||||||
},
|
},
|
||||||
|
getCurrencySymbol(currency) {
|
||||||
|
const code = (currency || '').toUpperCase()
|
||||||
|
if (code === 'BTC' || code === 'XBT' || code === 'SAT' || code === 'SATS') {
|
||||||
|
return '₿'
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parts = new Intl.NumberFormat(window.i18n.global.locale, {
|
||||||
|
style: 'currency',
|
||||||
|
currency: code
|
||||||
|
}).formatToParts(0)
|
||||||
|
const symbolPart = parts.find(part => part.type === 'currency')
|
||||||
|
return symbolPart?.value || code || '¤'
|
||||||
|
} catch (e) {
|
||||||
|
return code || '¤'
|
||||||
|
}
|
||||||
|
},
|
||||||
formatSat(value) {
|
formatSat(value) {
|
||||||
return new Intl.NumberFormat(window.i18n.global.locale).format(value)
|
return new Intl.NumberFormat(window.i18n.global.locale).format(value)
|
||||||
},
|
},
|
||||||
@@ -144,7 +160,7 @@ window._lnbitsUtils = {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
notifyApiError(error) {
|
async notifyApiError(error) {
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
return console.error(error)
|
return console.error(error)
|
||||||
}
|
}
|
||||||
@@ -154,6 +170,10 @@ window._lnbitsUtils = {
|
|||||||
500: 'negative'
|
500: 'negative'
|
||||||
}
|
}
|
||||||
let messages = error.response.data.detail
|
let messages = error.response.data.detail
|
||||||
|
if (!messages) {
|
||||||
|
const text = await error.response.data?.text()
|
||||||
|
messages = this.parseJSONSafe(text)?.detail
|
||||||
|
}
|
||||||
if (messages) {
|
if (messages) {
|
||||||
messages = Array.isArray(messages)
|
messages = Array.isArray(messages)
|
||||||
? messages.map(e => e.msg + ` (${e.loc?.join('/')})`)
|
? messages.map(e => e.msg + ` (${e.loc?.join('/')})`)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
@use 'themes';
|
@use 'themes';
|
||||||
@use 'borders';
|
@use 'borders';
|
||||||
@use 'background';
|
@use 'background';
|
||||||
|
@use 'cards';
|
||||||
@use 'home';
|
@use 'home';
|
||||||
|
|
||||||
[v-cloak] {
|
[v-cloak] {
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
@use 'sass:color';
|
||||||
|
@use 'sass:map';
|
||||||
|
@use 'themes';
|
||||||
|
|
||||||
|
$card-radius: 10px;
|
||||||
|
|
||||||
|
body.rounded-ui {
|
||||||
|
.q-card,
|
||||||
|
.q-btn {
|
||||||
|
border-radius: $card-radius;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@each $theme, $colors in themes.$themes {
|
||||||
|
$primary: map.get($colors, primary);
|
||||||
|
$secondary: map.get($colors, secondary);
|
||||||
|
$dark: map.get($colors, dark);
|
||||||
|
|
||||||
|
body[data-theme='#{$theme}'].card-gradient {
|
||||||
|
.q-card:not(
|
||||||
|
.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark
|
||||||
|
) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
color.adjust($primary, $alpha: -0.92),
|
||||||
|
color.adjust($secondary, $alpha: -0.94)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.q-drawer {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
165deg,
|
||||||
|
color.adjust($primary, $alpha: -0.94),
|
||||||
|
color.adjust($secondary, $alpha: -0.96)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body[data-theme='#{$theme}'].card-gradient.body--dark {
|
||||||
|
.q-card:not(
|
||||||
|
.q-dialog .q-card,
|
||||||
|
.lnbits__dialog-card,
|
||||||
|
.q-dialog-plugin--dark
|
||||||
|
) {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
color.adjust($dark, $lightness: 4%),
|
||||||
|
color.adjust($primary, $alpha: -0.93)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
.q-drawer {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
165deg,
|
||||||
|
color.adjust($dark, $lightness: 3%),
|
||||||
|
color.adjust($primary, $alpha: -0.95)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.card-shadow {
|
||||||
|
.q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||||
|
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body.card-shadow.body--dark {
|
||||||
|
.q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||||
|
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+782
-900
File diff suppressed because it is too large
Load Diff
Vendored
+131
-114
@@ -7,13 +7,13 @@
|
|||||||
exports.noConflict = function () { global._ = current; return exports; };
|
exports.noConflict = function () { global._ = current; return exports; };
|
||||||
}()));
|
}()));
|
||||||
}(this, (function () {
|
}(this, (function () {
|
||||||
// Underscore.js 1.13.7
|
// Underscore.js 1.13.8
|
||||||
// https://underscorejs.org
|
// https://underscorejs.org
|
||||||
// (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
|
// (c) 2009-2026 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
|
||||||
// Underscore may be freely distributed under the MIT license.
|
// Underscore may be freely distributed under the MIT license.
|
||||||
|
|
||||||
// Current version.
|
// Current version.
|
||||||
var VERSION = '1.13.7';
|
var VERSION = '1.13.8';
|
||||||
|
|
||||||
// Establish the root object, `window` (`self`) in the browser, `global`
|
// Establish the root object, `window` (`self`) in the browser, `global`
|
||||||
// on the server, or `this` in some virtual machines. We use `self`
|
// on the server, or `this` in some virtual machines. We use `self`
|
||||||
@@ -357,131 +357,146 @@
|
|||||||
// We use this string twice, so give it a name for minification.
|
// We use this string twice, so give it a name for minification.
|
||||||
var tagDataView = '[object DataView]';
|
var tagDataView = '[object DataView]';
|
||||||
|
|
||||||
// Internal recursive comparison function for `_.isEqual`.
|
// Perform a deep comparison to check if two objects are equal.
|
||||||
function eq(a, b, aStack, bStack) {
|
function isEqual(a, b) {
|
||||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
var todo = [{a: a, b: b}];
|
||||||
// See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
// Initializing stacks of traversed objects for cycle detection.
|
||||||
if (a === b) return a !== 0 || 1 / a === 1 / b;
|
var aStack = [], bStack = [];
|
||||||
// `null` or `undefined` only equal to itself (strict comparison).
|
|
||||||
if (a == null || b == null) return false;
|
while (todo.length) {
|
||||||
// `NaN`s are equivalent, but non-reflexive.
|
var frame = todo.pop();
|
||||||
if (a !== a) return b !== b;
|
if (frame === true) {
|
||||||
// Exhaust primitive checks
|
// Remove the first object from the stack of traversed objects.
|
||||||
var type = typeof a;
|
aStack.pop();
|
||||||
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
bStack.pop();
|
||||||
return deepEq(a, b, aStack, bStack);
|
continue;
|
||||||
}
|
}
|
||||||
|
a = frame.a;
|
||||||
|
b = frame.b;
|
||||||
|
|
||||||
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||||
|
// See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
||||||
|
if (a === b) {
|
||||||
|
if (a !== 0 || 1 / a === 1 / b) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// `null` or `undefined` only equal to itself (strict comparison).
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
// `NaN`s are equivalent, but non-reflexive.
|
||||||
|
if (a !== a) {
|
||||||
|
if (b !== b) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Exhaust primitive checks
|
||||||
|
var type = typeof a;
|
||||||
|
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
||||||
|
|
||||||
// Internal recursive comparison function for `_.isEqual`.
|
// Internal recursive comparison function for `_.isEqual`.
|
||||||
function deepEq(a, b, aStack, bStack) {
|
// Unwrap any wrapped objects.
|
||||||
// Unwrap any wrapped objects.
|
if (a instanceof _$1) a = a._wrapped;
|
||||||
if (a instanceof _$1) a = a._wrapped;
|
if (b instanceof _$1) b = b._wrapped;
|
||||||
if (b instanceof _$1) b = b._wrapped;
|
// Compare `[[Class]]` names.
|
||||||
// Compare `[[Class]]` names.
|
var className = toString.call(a);
|
||||||
var className = toString.call(a);
|
if (className !== toString.call(b)) return false;
|
||||||
if (className !== toString.call(b)) return false;
|
// Work around a bug in IE 10 - Edge 13.
|
||||||
// Work around a bug in IE 10 - Edge 13.
|
if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
|
||||||
if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
|
if (!isDataView$1(b)) return false;
|
||||||
if (!isDataView$1(b)) return false;
|
className = tagDataView;
|
||||||
className = tagDataView;
|
}
|
||||||
}
|
switch (className) {
|
||||||
switch (className) {
|
// These types are compared by value.
|
||||||
// These types are compared by value.
|
|
||||||
case '[object RegExp]':
|
case '[object RegExp]':
|
||||||
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
||||||
case '[object String]':
|
case '[object String]':
|
||||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||||
// equivalent to `new String("5")`.
|
// equivalent to `new String("5")`.
|
||||||
return '' + a === '' + b;
|
if ('' + a === '' + b) continue;
|
||||||
|
return false;
|
||||||
case '[object Number]':
|
case '[object Number]':
|
||||||
// `NaN`s are equivalent, but non-reflexive.
|
todo.push({a: +a, b: +b});
|
||||||
// Object(NaN) is equivalent to NaN.
|
continue;
|
||||||
if (+a !== +a) return +b !== +b;
|
|
||||||
// An `egal` comparison is performed for other numeric values.
|
|
||||||
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
|
|
||||||
case '[object Date]':
|
case '[object Date]':
|
||||||
case '[object Boolean]':
|
case '[object Boolean]':
|
||||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||||
// millisecond representations. Note that invalid dates with millisecond representations
|
// millisecond representations. Note that invalid dates with millisecond representations
|
||||||
// of `NaN` are not equivalent.
|
// of `NaN` are not equivalent.
|
||||||
return +a === +b;
|
if (+a === +b) continue;
|
||||||
|
return false;
|
||||||
case '[object Symbol]':
|
case '[object Symbol]':
|
||||||
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
|
if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
|
||||||
|
return false;
|
||||||
case '[object ArrayBuffer]':
|
case '[object ArrayBuffer]':
|
||||||
case tagDataView:
|
case tagDataView:
|
||||||
// Coerce to typed array so we can fall through.
|
// Coerce to typed array so we can fall through.
|
||||||
return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
|
todo.push({a: toBufferView(a), b: toBufferView(b)});
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var areArrays = className === '[object Array]';
|
var areArrays = className === '[object Array]';
|
||||||
if (!areArrays && isTypedArray$1(a)) {
|
if (!areArrays && isTypedArray$1(a)) {
|
||||||
var byteLength = getByteLength(a);
|
var byteLength = getByteLength(a);
|
||||||
if (byteLength !== getByteLength(b)) return false;
|
if (byteLength !== getByteLength(b)) return false;
|
||||||
if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
|
if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
|
||||||
areArrays = true;
|
areArrays = true;
|
||||||
}
|
|
||||||
if (!areArrays) {
|
|
||||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
|
||||||
|
|
||||||
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
|
||||||
// from different frames are.
|
|
||||||
var aCtor = a.constructor, bCtor = b.constructor;
|
|
||||||
if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
|
|
||||||
isFunction$1(bCtor) && bCtor instanceof bCtor)
|
|
||||||
&& ('constructor' in a && 'constructor' in b)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
if (!areArrays) {
|
||||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
|
||||||
|
|
||||||
// Initializing stack of traversed objects.
|
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
||||||
// It's done here since we only need them for objects and arrays comparison.
|
// from different frames are.
|
||||||
aStack = aStack || [];
|
var aCtor = a.constructor, bCtor = b.constructor;
|
||||||
bStack = bStack || [];
|
if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
|
||||||
var length = aStack.length;
|
isFunction$1(bCtor) && bCtor instanceof bCtor)
|
||||||
while (length--) {
|
&& ('constructor' in a && 'constructor' in b)) {
|
||||||
// Linear search. Performance is inversely proportional to the number of
|
return false;
|
||||||
// unique nested structures.
|
}
|
||||||
if (aStack[length] === a) return bStack[length] === b;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add the first object to the stack of traversed objects.
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||||
aStack.push(a);
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||||
bStack.push(b);
|
|
||||||
|
|
||||||
// Recursively compare objects and arrays.
|
var length = aStack.length;
|
||||||
if (areArrays) {
|
|
||||||
// Compare array lengths to determine if a deep comparison is necessary.
|
|
||||||
length = a.length;
|
|
||||||
if (length !== b.length) return false;
|
|
||||||
// Deep compare the contents, ignoring non-numeric properties.
|
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (!eq(a[length], b[length], aStack, bStack)) return false;
|
// Linear search. Performance is inversely proportional to the number of
|
||||||
|
// unique nested structures.
|
||||||
|
if (aStack[length] === a) {
|
||||||
|
if (bStack[length] === b) break;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if (length >= 0) continue;
|
||||||
// Deep compare objects.
|
|
||||||
var _keys = keys(a), key;
|
// Add the first object to the stack of traversed objects.
|
||||||
length = _keys.length;
|
aStack.push(a);
|
||||||
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
bStack.push(b);
|
||||||
if (keys(b).length !== length) return false;
|
todo.push(true);
|
||||||
while (length--) {
|
|
||||||
// Deep compare each member
|
// Recursively compare objects and arrays.
|
||||||
key = _keys[length];
|
if (areArrays) {
|
||||||
if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
|
// Compare array lengths to determine if a deep comparison is necessary.
|
||||||
|
length = a.length;
|
||||||
|
if (length !== b.length) return false;
|
||||||
|
// Deep compare the contents, ignoring non-numeric properties.
|
||||||
|
while (length--) {
|
||||||
|
todo.push({a: a[length], b: b[length]});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deep compare objects.
|
||||||
|
var _keys = keys(a), key;
|
||||||
|
length = _keys.length;
|
||||||
|
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
||||||
|
if (keys(b).length !== length) return false;
|
||||||
|
while (length--) {
|
||||||
|
// Deep compare each member
|
||||||
|
key = _keys[length];
|
||||||
|
if (!has$1(b, key)) return false;
|
||||||
|
todo.push({a: a[key], b: b[key]});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove the first object from the stack of traversed objects.
|
|
||||||
aStack.pop();
|
|
||||||
bStack.pop();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform a deep comparison to check if two objects are equal.
|
|
||||||
function isEqual(a, b) {
|
|
||||||
return eq(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve all the enumerable property names of an object.
|
// Retrieve all the enumerable property names of an object.
|
||||||
function allKeys(obj) {
|
function allKeys(obj) {
|
||||||
if (!isObject(obj)) return [];
|
if (!isObject(obj)) return [];
|
||||||
@@ -1032,25 +1047,27 @@
|
|||||||
var isArrayLike = createSizePropertyCheck(getLength);
|
var isArrayLike = createSizePropertyCheck(getLength);
|
||||||
|
|
||||||
// Internal implementation of a recursive `flatten` function.
|
// Internal implementation of a recursive `flatten` function.
|
||||||
function flatten$1(input, depth, strict, output) {
|
function flatten$1(input, depth, strict) {
|
||||||
output = output || [];
|
if (!depth && depth !== 0) depth = Infinity;
|
||||||
if (!depth && depth !== 0) {
|
var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
|
||||||
depth = Infinity;
|
while (true) {
|
||||||
} else if (depth <= 0) {
|
if (i >= length) {
|
||||||
return output.concat(input);
|
if (!stack.length) break;
|
||||||
}
|
var frame = stack.pop();
|
||||||
var idx = output.length;
|
i = frame.i;
|
||||||
for (var i = 0, length = getLength(input); i < length; i++) {
|
input = frame.v;
|
||||||
var value = input[i];
|
length = getLength(input);
|
||||||
if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
|
continue;
|
||||||
|
}
|
||||||
|
var value = input[i++];
|
||||||
|
if (stack.length >= depth) {
|
||||||
|
output[idx++] = value;
|
||||||
|
} else if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
|
||||||
// Flatten current level of array or arguments object.
|
// Flatten current level of array or arguments object.
|
||||||
if (depth > 1) {
|
stack.push({i: i, v: input});
|
||||||
flatten$1(value, depth - 1, strict, output);
|
i = 0;
|
||||||
idx = output.length;
|
input = value;
|
||||||
} else {
|
length = getLength(input);
|
||||||
var j = 0, len = value.length;
|
|
||||||
while (j < len) output[idx++] = value[j++];
|
|
||||||
}
|
|
||||||
} else if (!strict) {
|
} else if (!strict) {
|
||||||
output[idx++] = value;
|
output[idx++] = value;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -178,7 +178,11 @@ async def invoice_callback_dispatcher(checking_id: str, is_internal: bool = Fals
|
|||||||
logger.warning(f"Payment '{checking_id}' is not incoming, skipping.")
|
logger.warning(f"Payment '{checking_id}' is not incoming, skipping.")
|
||||||
return
|
return
|
||||||
|
|
||||||
status = await payment.check_status(skip_internal_payment_notifications=True)
|
from lnbits.core.services.payments import check_payment_status
|
||||||
|
|
||||||
|
status = await check_payment_status(
|
||||||
|
payment, skip_internal_payment_notifications=True
|
||||||
|
)
|
||||||
payment.fee = status.fee_msat or payment.fee
|
payment.fee = status.fee_msat or payment.fee
|
||||||
# only overwrite preimage if status.preimage provides it
|
# only overwrite preimage if status.preimage provides it
|
||||||
payment.preimage = status.preimage or payment.preimage
|
payment.preimage = status.preimage or payment.preimage
|
||||||
|
|||||||
@@ -70,10 +70,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const RENDERED_ROUTE = '{{ normalize_path(request.path) }}'
|
const RENDERED_ROUTE = '{{ normalize_path(request.path) }}'
|
||||||
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
const SETTINGS = {{ SETTINGS | tojson | safe }}
|
||||||
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
const CURRENCIES = {{ CURRENCIES | tojson | safe }}
|
||||||
window[key] = WINDOW_SETTINGS[key]
|
// backwards compatibility for extensions (tpos)
|
||||||
})
|
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson | safe }}
|
||||||
</script>
|
</script>
|
||||||
<!-- vue templates -->
|
<!-- vue templates -->
|
||||||
{% include('components.vue') %} {% include('pages.vue') %}
|
{% include('components.vue') %} {% include('pages.vue') %}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
||||||
<q-item-label header v-text="$t('manage')"></q-item-label>
|
<q-item-label header v-text="$t('manage')"></q-item-label>
|
||||||
<div v-if="g.user.admin">
|
<div v-if="g.user.admin">
|
||||||
<q-item v-if="showAdmin" to="/admin">
|
<q-item v-if="g.settings.showAdmin" to="/admin">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="settings"
|
name="settings"
|
||||||
@@ -51,7 +51,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-if="showNode" to="/node">
|
<q-item v-if="g.settings.showNodemanager" to="/node">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="developer_board"
|
name="developer_board"
|
||||||
@@ -66,7 +66,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-if="showUsers" to="/users">
|
<q-item v-if="g.settings.showAdmin" to="/users">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="groups"
|
name="groups"
|
||||||
@@ -81,7 +81,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-if="showAudit" to="/audit">
|
<q-item v-if="g.settings.showAudit" to="/audit">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="playlist_add_check_circle"
|
name="playlist_add_check_circle"
|
||||||
@@ -112,7 +112,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item v-if="showExtensions" to="/extensions">
|
<q-item v-if="g.settings.showExtensions" to="/extensions">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="extension"
|
name="extension"
|
||||||
@@ -686,7 +686,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
v-model="walletName"
|
v-model="walletName"
|
||||||
:label="$t('name_your_wallet', {name: SITE_TITLE + ' *'})"
|
:label="$t('name_your_wallet', {name: g.settings.siteTitle + ' *'})"
|
||||||
></q-input>
|
></q-input>
|
||||||
<q-card-actions vertical align="center" class="q-pa-none">
|
<q-card-actions vertical align="center" class="q-pa-none">
|
||||||
<q-btn
|
<q-btn
|
||||||
@@ -814,16 +814,90 @@ include('components/lnbits-error.vue') %}
|
|||||||
type="password"
|
type="password"
|
||||||
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
|
:rules="[val => !val || val.length >= 8 || $t('invalid_password')]"
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<div
|
||||||
|
v-if="confirmationMethodsCount > 1"
|
||||||
|
class="row justify-center q-mb-md"
|
||||||
|
>
|
||||||
|
<q-tabs
|
||||||
|
v-model="confirmationMethod"
|
||||||
|
dense
|
||||||
|
active-color="primary"
|
||||||
|
indicator-color="primary"
|
||||||
|
>
|
||||||
|
<q-tab
|
||||||
|
v-if="g.settings.userActivationByInvitationCode"
|
||||||
|
name="code"
|
||||||
|
icon="confirmation_number"
|
||||||
|
label="Code"
|
||||||
|
></q-tab>
|
||||||
|
<q-tab
|
||||||
|
v-if="g.settings.userActivationByPayment"
|
||||||
|
name="payment"
|
||||||
|
icon="bolt"
|
||||||
|
label="Payment"
|
||||||
|
></q-tab>
|
||||||
|
<q-tab
|
||||||
|
v-if="g.settings.userActivationByEmail"
|
||||||
|
name="email"
|
||||||
|
icon="email"
|
||||||
|
label="Email"
|
||||||
|
></q-tab>
|
||||||
|
</q-tabs>
|
||||||
|
</div>
|
||||||
|
<div v-if="confirmationMethodsCount > 0" class="q-mb-md">
|
||||||
|
<q-tab-panels v-model="confirmationMethod">
|
||||||
|
<q-tab-panel name="code" class="q-pa-none">
|
||||||
|
<div
|
||||||
|
class="q-my-md q-pa-sm text-body2 text-grey-4 bg-grey-9 rounded-borders"
|
||||||
|
>
|
||||||
|
<q-icon name="info" color="orange-4" class="q-mr-xs"></q-icon>
|
||||||
|
You need an invitation code to register.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
v-model="confirmationCode"
|
||||||
|
:label="$t('invitation_code')"
|
||||||
|
:type="showConfirmationCode ? 'text' : 'password'"
|
||||||
|
:hint="$t('invitation_code_hint')"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
@click="showConfirmationCode = !showConfirmationCode"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
:icon="
|
||||||
|
showConfirmationCode ? 'visibility_off' : 'visibility'
|
||||||
|
"
|
||||||
|
color="grey"
|
||||||
|
></q-btn>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</q-tab-panel>
|
||||||
|
<q-tab-panel name="payment">
|
||||||
|
<div>payment</div>
|
||||||
|
</q-tab-panel>
|
||||||
|
|
||||||
|
<q-tab-panel name="email" class="q-pa-none">
|
||||||
|
<div>
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
v-model="confirmationEmail"
|
||||||
|
:label="$t('email')"
|
||||||
|
:hint="$t('email_confirmation_hint')"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</q-tab-panel>
|
||||||
|
</q-tab-panels>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row justify-end">
|
<div class="row justify-end">
|
||||||
<q-btn
|
<q-btn
|
||||||
unelevated
|
unelevated
|
||||||
color="primary"
|
color="primary"
|
||||||
:disable="
|
:disable="disableRegister"
|
||||||
!password ||
|
|
||||||
!passwordRepeat ||
|
|
||||||
!username ||
|
|
||||||
password !== passwordRepeat
|
|
||||||
"
|
|
||||||
type="submit"
|
type="submit"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
:label="$t('create_account')"
|
:label="$t('create_account')"
|
||||||
@@ -960,9 +1034,9 @@ include('components/lnbits-error.vue') %}
|
|||||||
<q-avatar size="32px" class="q-mr-md">
|
<q-avatar size="32px" class="q-mr-md">
|
||||||
<q-img
|
<q-img
|
||||||
:src="
|
:src="
|
||||||
keycloakIcon
|
g.settings.keycloakIcon
|
||||||
? keycloakIcon
|
? g.settings.keycloakIcon
|
||||||
: 'lnbits/static/images/keycloak-logo.png'
|
: utils.url_for('lnbits/static/images/keycloak-logo.png')
|
||||||
"
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
@@ -970,7 +1044,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<span
|
<span
|
||||||
v-text="
|
v-text="
|
||||||
$t('signin_with_custom_org', {
|
$t('signin_with_custom_org', {
|
||||||
custom_org: keycloakOrg
|
custom_org: g.settings.keycloakOrg || 'Keycloak'
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
></span>
|
></span>
|
||||||
|
|||||||
@@ -3,9 +3,6 @@
|
|||||||
<h6 class="q-my-none q-mb-sm">Assets</h6>
|
<h6 class="q-my-none q-mb-sm">Assets</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<p>
|
|
||||||
<span v-text="$t('max_asset_size_mb')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -18,9 +15,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-9">
|
||||||
<p>
|
|
||||||
<span v-text="$t('assets_allowed_mime_types')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model.number="newAllowedAssetMimeType"
|
v-model.number="newAllowedAssetMimeType"
|
||||||
@@ -56,11 +50,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||||
<h6 class="q-my-none q-mb-sm">Thumbnails</h6>
|
<h6 class="q-my-none q-mb-sm">Thumbnails</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md q-my-lg">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('thumbnail_width')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -71,10 +62,7 @@
|
|||||||
:hint="$t('thumbnail_width_desc')"
|
:hint="$t('thumbnail_width_desc')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('thumbnail_height')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -85,10 +73,7 @@
|
|||||||
:hint="$t('thumbnail_height_desc')"
|
:hint="$t('thumbnail_height_desc')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('thumbnail_format')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model.number="formData.lnbits_asset_thumbnail_format"
|
v-model.number="formData.lnbits_asset_thumbnail_format"
|
||||||
@@ -101,9 +86,6 @@
|
|||||||
<h6 class="q-my-none q-mb-sm">Users</h6>
|
<h6 class="q-my-none q-mb-sm">Users</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<p>
|
|
||||||
<span v-text="$t('max_assets_per_user')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -115,9 +97,6 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-9">
|
<div class="col-12 col-md-9">
|
||||||
<p>
|
|
||||||
<span v-text="$t('assets_no_limit_users')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model="newNoLimitUser"
|
v-model="newNoLimitUser"
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
@@ -129,7 +129,6 @@
|
|||||||
<q-separator class="q-mb-xl q-mt-sm"></q-separator>
|
<q-separator class="q-mb-xl q-mt-sm"></q-separator>
|
||||||
<div class="row q-mb-lg">
|
<div class="row q-mb-lg">
|
||||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<p><span v-text="$t('audit_http_methods')"></span></p>
|
|
||||||
<q-select
|
<q-select
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_audit_http_methods"
|
v-model="formData.lnbits_audit_http_methods"
|
||||||
@@ -148,7 +147,6 @@
|
|||||||
></q-select>
|
></q-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<p><span v-text="$t('audit_resp_codes')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
filled
|
filled
|
||||||
@@ -177,7 +175,6 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||||
<p><span v-text="$t('audit_paths')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
filled
|
filled
|
||||||
@@ -204,9 +201,6 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 col-sm-12">
|
<div class="col-md-6 col-sm-12">
|
||||||
<p>
|
|
||||||
<span v-text="$t('audit_paths_exclude')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
filled
|
filled
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<q-input
|
<q-input
|
||||||
|
class="q-mb-md"
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_exchange_history_refresh_interval_seconds"
|
v-model="formData.lnbits_exchange_history_refresh_interval_seconds"
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
:hint="$t('extension_builder_manifest_url_hint')"
|
:hint="$t('extension_builder_manifest_url_hint')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6 q-pb-md">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="$t('reviews_url')"></span>
|
<span v-text="$t('reviews_url')"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-expansion-item :label="$t('service_fee')">
|
<q-expansion-item :label="$t('service_fees')">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-4 col-sm-12">
|
<div class="col-md-4 col-sm-12">
|
||||||
@@ -140,7 +140,6 @@
|
|||||||
v-model="formData.stripe_limits.service_fee_percent"
|
v-model="formData.stripe_limits.service_fee_percent"
|
||||||
@update:model-value="formData.touch = null"
|
@update:model-value="formData.touch = null"
|
||||||
:label="$t('service_fee_label')"
|
:label="$t('service_fee_label')"
|
||||||
:hint="$t('service_fee_hint')"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-sm-12">
|
<div class="col-md-4 col-sm-12">
|
||||||
@@ -151,8 +150,7 @@
|
|||||||
min="0"
|
min="0"
|
||||||
v-model="formData.stripe_limits.service_max_fee_sats"
|
v-model="formData.stripe_limits.service_max_fee_sats"
|
||||||
@update:model-value="formData.touch = null"
|
@update:model-value="formData.touch = null"
|
||||||
:label="$t('service_fee_max')"
|
:label="$t('service_fee_max_label')"
|
||||||
:hint="$t('service_fee_max_hint')"
|
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 col-sm-12">
|
<div class="col-md-4 col-sm-12">
|
||||||
@@ -408,7 +406,6 @@
|
|||||||
<li><code>CHECKOUT.ORDER.APPROVED</code></li>
|
<li><code>CHECKOUT.ORDER.APPROVED</code></li>
|
||||||
<li><code>PAYMENT.CAPTURE.COMPLETED</code></li>
|
<li><code>PAYMENT.CAPTURE.COMPLETED</code></li>
|
||||||
<li><code>PAYMENT.SALE.COMPLETED</code></li>
|
<li><code>PAYMENT.SALE.COMPLETED</code></li>
|
||||||
<li><code>BILLING.SUBSCRIPTION.PAYMENT.SUCCEEDED</code></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div v-if="LNBITS_NODE_UI">
|
<div v-if="g.settings.hasNodemanager">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="$t('node_management')"></span>
|
<span v-text="$t('node_management')"></span>
|
||||||
</p>
|
</p>
|
||||||
@@ -70,53 +70,81 @@
|
|||||||
v-model="formData.lnbits_node_ui_transactions"
|
v-model="formData.lnbits_node_ui_transactions"
|
||||||
></q-toggle>
|
></q-toggle>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!LNBITS_NODE_UI">
|
<p v-if="!g.settings.hasNodemanager">
|
||||||
<span v-text="$t('node_management_not_supported')"></span>
|
<span v-text="$t('node_management_not_supported')"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12">
|
||||||
<p><span v-text="$t('fee_reserve')"></span></p>
|
<h6 class="q-my-none">
|
||||||
|
<span v-text="$t('routing_fee_reserve_calculations')"></span>
|
||||||
|
</h6>
|
||||||
|
<p class="q-mt-sm q-mb-none">
|
||||||
|
<span v-html="$t('routing_fee_reserve_calculations_desc')"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<p>
|
||||||
|
<span v-text="$t('fee_reserve')"></span>
|
||||||
|
<sup>
|
||||||
|
<q-icon name="info" size="16px" class="q-ml-xs"></q-icon>
|
||||||
|
<q-tooltip max-width="300px">
|
||||||
|
<span v-html="$t('fee_reserve_min_hint')"></span>
|
||||||
|
</q-tooltip>
|
||||||
|
</sup>
|
||||||
|
</p>
|
||||||
<q-input
|
<q-input
|
||||||
type="number"
|
type="number"
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_reserve_fee_min"
|
v-model="formData.lnbits_reserve_fee_min"
|
||||||
:label="$t('fee_reserve_msats')"
|
:suffix="$t('millisats')"
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="$t('fee_reserve_percent')"></span>
|
<span v-text="$t('fee_reserve_percent')"></span>
|
||||||
|
<sup>
|
||||||
|
<q-icon name="info" size="16px" class="q-ml-xs"></q-icon>
|
||||||
|
<q-tooltip max-width="300px">
|
||||||
|
<span v-html="$t('fee_reserve_percent_hint')"></span>
|
||||||
|
</q-tooltip>
|
||||||
|
</sup>
|
||||||
</p>
|
</p>
|
||||||
<q-input
|
<q-input
|
||||||
type="number"
|
type="number"
|
||||||
filled
|
filled
|
||||||
name="lnbits_reserve_fee_percent"
|
name="lnbits_reserve_fee_percent"
|
||||||
v-model="formData.lnbits_reserve_fee_percent"
|
v-model="formData.lnbits_reserve_fee_percent"
|
||||||
:label="$t('reserve_fee_in_percent')"
|
|
||||||
step="0.1"
|
step="0.1"
|
||||||
|
suffix="%"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
</div>
|
||||||
|
<div class="row q-col-gutter-md q-mt-sm">
|
||||||
|
<div class="col-12">
|
||||||
|
<h6 class="q-my-none">
|
||||||
|
<span v-text="$t('payment_timeouts')"></span>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
<p><span v-text="$t('invoice_expiry')"></span></p>
|
<p><span v-text="$t('invoice_expiry')"></span></p>
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model.number="formData.lightning_invoice_expiry"
|
v-model.number="formData.lightning_invoice_expiry"
|
||||||
type="number"
|
type="number"
|
||||||
:label="$t('invoice_expiry_label')"
|
:suffix="$t('seconds')"
|
||||||
mask="#######"
|
mask="#######"
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="$t('payment_wait_time')"></span>
|
<span v-text="$t('payment_wait_time')"></span>
|
||||||
<sup>
|
<sup>
|
||||||
<q-icon name="info" size="16px" class="q-ml-xs"></q-icon>
|
<q-icon name="info" size="16px" class="q-ml-xs"></q-icon>
|
||||||
<q-tooltip max-width="150px">
|
<q-tooltip max-width="300px">
|
||||||
<span v-text="$t('payment_wait_time_tooltip')"></span>
|
<span v-text="$t('payment_wait_time_tooltip')"></span>
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</sup>
|
</sup>
|
||||||
@@ -126,8 +154,8 @@
|
|||||||
filled
|
filled
|
||||||
name="lnbits_funding_source_pay_invoice_wait_seconds"
|
name="lnbits_funding_source_pay_invoice_wait_seconds"
|
||||||
v-model="formData.lnbits_funding_source_pay_invoice_wait_seconds"
|
v-model="formData.lnbits_funding_source_pay_invoice_wait_seconds"
|
||||||
:label="$t('payment_wait_time')"
|
|
||||||
:hint="$t('payment_wait_time_desc')"
|
:hint="$t('payment_wait_time_desc')"
|
||||||
|
:suffix="$t('seconds')"
|
||||||
step="1"
|
step="1"
|
||||||
min="0"
|
min="0"
|
||||||
></q-input>
|
></q-input>
|
||||||
@@ -165,6 +193,11 @@
|
|||||||
<h6 class="q-mt-lg q-mb-sm">
|
<h6 class="q-mt-lg q-mb-sm">
|
||||||
<p v-text="$t('watchdog')"></p>
|
<p v-text="$t('watchdog')"></p>
|
||||||
</h6>
|
</h6>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<p v-text="$t('watchdog_introduction')"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
@@ -239,6 +272,7 @@
|
|||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_watchdog_delta"
|
v-model="formData.lnbits_watchdog_delta"
|
||||||
|
:suffix="$t('sats')"
|
||||||
type="number"
|
type="number"
|
||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|||||||
@@ -12,11 +12,14 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<p>Active Funding<small> (Requires server restart)</small></p>
|
<p>
|
||||||
|
<span v-text="$t('funding_source')"></span>
|
||||||
|
<small><span v-text="$t('requires_server_restart')"></span></small>
|
||||||
|
</p>
|
||||||
<q-select
|
<q-select
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_backend_wallet_class"
|
v-model="formData.lnbits_backend_wallet_class"
|
||||||
hint="Select the active funding wallet"
|
:hint="$t('funding_source_info')"
|
||||||
:options="sortedAllowedFundingSources"
|
:options="sortedAllowedFundingSources"
|
||||||
:option-label="item => getFundingSourceLabel(item)"
|
:option-label="item => getFundingSourceLabel(item)"
|
||||||
></q-select>
|
></q-select>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<template id="lnbits-admin-security">
|
<template id="lnbits-admin-security">
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<h6 class="q-my-none">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('server_management')"></span>
|
<span v-text="$t('server_management')"></span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('base_url')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model.number="formData.lnbits_baseurl"
|
v-model.number="formData.lnbits_baseurl"
|
||||||
@@ -17,7 +16,7 @@
|
|||||||
<h6 class="q-my-none q-mb-sm">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('authentication')"></span>
|
<span v-text="$t('authentication')"></span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row q-col-gutter-sm">
|
<div class="row q-col-gutter-sm q-mb-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
@@ -305,12 +304,19 @@
|
|||||||
type="number"
|
type="number"
|
||||||
v-model.number="formData.lnbits_rate_limit_no"
|
v-model.number="formData.lnbits_rate_limit_no"
|
||||||
:label="$t('number_of_requests')"
|
:label="$t('number_of_requests')"
|
||||||
|
:hint="$t('number_of_requests_hint')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<q-select
|
<q-select
|
||||||
filled
|
filled
|
||||||
:options="[$t('second'), $t('minute'), $t('hour')]"
|
:options="[
|
||||||
|
{label: $t('second'), value: 'second'},
|
||||||
|
{label: $t('minute'), value: 'minute'},
|
||||||
|
{label: $t('hour'), value: 'hour'}
|
||||||
|
]"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
v-model="formData.lnbits_rate_limit_unit"
|
v-model="formData.lnbits_rate_limit_unit"
|
||||||
:label="$t('time_unit')"
|
:label="$t('time_unit')"
|
||||||
></q-select>
|
></q-select>
|
||||||
|
|||||||
@@ -38,14 +38,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||||
<h6 class="q-my-none">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('payments')"></span>
|
<span v-text="$t('payments')"></span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('max_outgoing_payment_amount')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -57,10 +54,7 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('max_incoming_payment_amount')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
@@ -75,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||||
<h6 class="q-my-none">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('wallet_limiter')"></span>
|
<span v-text="$t('wallet_limiter')"></span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
@@ -113,35 +107,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||||
<h6 class="q-my-none">
|
<h6 class="q-my-none q-mb-sm">
|
||||||
<span v-text="$t('service_fee')"></span>
|
<span v-text="$t('service_fees')"></span>
|
||||||
</h6>
|
</h6>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-4">
|
||||||
<p><span v-text="$t('service_fee')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="formData.lnbits_service_fee"
|
v-model.number="formData.lnbits_service_fee"
|
||||||
:label="$t('service_fee_label')"
|
:label="$t('service_fee_label')"
|
||||||
step="0.1"
|
step="0.1"
|
||||||
:hint="$t('service_fee_hint')"
|
:suffix="'%'"
|
||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-4">
|
||||||
<p><span v-text="$t('service_fee_max')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="formData.lnbits_service_fee_max"
|
v-model.number="formData.lnbits_service_fee_max"
|
||||||
:label="$t('service_fee_max_label')"
|
:label="$t('service_fee_max_label')"
|
||||||
:hint="$t('service_fee_max_hint')"
|
:suffix="$t('sats')"
|
||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('fee_wallet')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_service_fee_wallet"
|
v-model="formData.lnbits_service_fee_wallet"
|
||||||
@@ -151,16 +142,9 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p>
|
|
||||||
<span v-text="$t('disable_fee_internal')"></span>
|
|
||||||
</p>
|
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label v-text="$t('disable_fee')"></q-item-label>
|
<q-item-label v-text="$t('disable_fee')"></q-item-label>
|
||||||
<q-item-label
|
|
||||||
caption
|
|
||||||
v-text="$t('disable_fee_internal_desc')"
|
|
||||||
></q-item-label>
|
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section avatar>
|
<q-item-section avatar>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-5">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('ui_site_title')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
@@ -18,8 +17,7 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-5">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('ui_site_tagline')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
@@ -28,7 +26,9 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-2 q-mt-xl">
|
</div>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12 col-md-12">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
:tip="$t('ui_toggle_elements_tip')"
|
:tip="$t('ui_toggle_elements_tip')"
|
||||||
v-model="formData.lnbits_show_home_page_elements"
|
v-model="formData.lnbits_show_home_page_elements"
|
||||||
@@ -40,66 +40,96 @@
|
|||||||
></q-toggle>
|
></q-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<span v-text="$t('ui_site_description')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
|
||||||
v-model="formData.lnbits_site_description"
|
|
||||||
filled
|
|
||||||
type="textarea"
|
|
||||||
:hint="$t('ui_site_description_hint')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<br />
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input
|
||||||
|
v-model="formData.lnbits_site_description"
|
||||||
|
filled
|
||||||
|
type="textarea"
|
||||||
|
:label="$t('ui_site_description')"
|
||||||
|
:hint="$t('ui_site_description_hint')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<p>
|
|
||||||
<span v-text="$t('ui_default_wallet_name')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_default_wallet_name"
|
v-model="formData.lnbits_default_wallet_name"
|
||||||
:label="$t('lnbits_wallet')"
|
:label="$t('ui_default_wallet_name')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<p><span v-text="$t('ui_qr_code_logo')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_qr_logo"
|
v-model="formData.lnbits_qr_logo"
|
||||||
label="https://example.com/image.svg"
|
:label="$t('ui_qr_code_logo')"
|
||||||
:hint="$t('ui_qr_code_logo_hint')"
|
:hint="$t('ui_qr_code_logo_hint')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-4">
|
||||||
<p><span v-text="$t('ui_apple_touch_icon')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_apple_touch_icon"
|
v-model="formData.lnbits_apple_touch_icon"
|
||||||
label="https://example.com/image.png"
|
:label="$t('ui_apple_touch_icon')"
|
||||||
:hint="$t('ui_apple_touch_icon_hint')"
|
:hint="$t('ui_apple_touch_icon_hint')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12 col-md-12">
|
||||||
|
<p><span v-text="$t('wallet_featured_button_title')"></span></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="text"
|
||||||
|
v-model="formData.lnbits_wallet_featured_button_label"
|
||||||
|
:label="$t('wallet_featured_button_label')"
|
||||||
|
:hint="$t('wallet_featured_button_label_hint')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="text"
|
||||||
|
v-model="formData.lnbits_wallet_featured_button_url"
|
||||||
|
:label="$t('wallet_featured_button_url')"
|
||||||
|
:hint="$t('wallet_featured_button_url_hint')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-4">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="text"
|
||||||
|
v-model="formData.lnbits_wallet_featured_button_icon"
|
||||||
|
label="bolt"
|
||||||
|
:hint="$t('wallet_featured_button_icon_hint')"
|
||||||
|
></q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row q-col-gutter-md q-mt-md">
|
<div class="row q-col-gutter-md q-mt-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-12">
|
||||||
<p><span v-text="$t('ui_custom_badge')"></span></p>
|
<h6 class="q-my-sm">
|
||||||
|
<span v-text="$t('ui_custom_badge_title')"></span>
|
||||||
|
</h6>
|
||||||
|
<p class="q-mb-none">
|
||||||
|
<span v-text="$t('ui_custom_badge_desc')"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-8">
|
<div class="col-12 col-md-6">
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
tip="Custom Badge"
|
tip="Custom Badge"
|
||||||
v-model.trim="formData.lnbits_custom_badge"
|
v-model.trim="formData.lnbits_custom_badge"
|
||||||
:label="$t('ui_custom_badge_label')"
|
:label="$t('ui_custom_badge')"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-4">
|
<div class="col-12 col-md-6">
|
||||||
<q-select
|
<q-select
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_custom_badge_color"
|
v-model="formData.lnbits_custom_badge_color"
|
||||||
@@ -109,8 +139,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12">
|
||||||
<p><span v-text="$t('ui_custom_image')"></span></p>
|
<h6 class="q-my-sm"><span v-text="$t('ui_custom_image')"></span></h6>
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
@@ -121,10 +151,8 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('themes')"></span></p>
|
|
||||||
<q-select
|
<q-select
|
||||||
filled
|
filled
|
||||||
v-model="formData.lnbits_theme_options"
|
v-model="formData.lnbits_theme_options"
|
||||||
@@ -136,41 +164,26 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('custom_logo')"></span></p>
|
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_custom_logo"
|
v-model="formData.lnbits_custom_logo"
|
||||||
label="https://example.com/image.png"
|
:label="$t('custom_logo')"
|
||||||
:hint="$t('custom_logo_hint')"
|
:hint="$t('custom_logo_hint')"
|
||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-12">
|
||||||
<p><span v-text="$t('ad_space_title')"></span></p>
|
<h6 class="q-my-sm">
|
||||||
<q-input
|
<span v-text="$t('ad_space_section_title')"></span>
|
||||||
filled
|
</h6>
|
||||||
type="text"
|
<p class="q-my-none">
|
||||||
v-model="formData.lnbits_ad_space_title"
|
<span v-text="$t('ad_space_section_desc')"></span>
|
||||||
:label="$t('ad_space_title_label')"
|
</p>
|
||||||
></q-input>
|
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-12">
|
||||||
<p><span v-text="$t('ad_slots')"></span></p>
|
|
||||||
<q-input
|
|
||||||
class="q-mb-md"
|
|
||||||
filled
|
|
||||||
v-model="formData.lnbits_ad_space"
|
|
||||||
type="text"
|
|
||||||
:label="$t('ad_slots_label')"
|
|
||||||
:hint="$t('ad_slots_hint')"
|
|
||||||
><q-tooltip>
|
|
||||||
format {url};{img-light};{img-dark},{url};{img-light};{img-dark}"
|
|
||||||
</q-tooltip>
|
|
||||||
</q-input>
|
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="formData.lnbits_ad_space_enabled"
|
v-model="formData.lnbits_ad_space_enabled"
|
||||||
:label="
|
:label="
|
||||||
@@ -179,8 +192,30 @@
|
|||||||
: $t('ads_disabled')
|
: $t('ads_disabled')
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
type="text"
|
||||||
|
v-model="formData.lnbits_ad_space_title"
|
||||||
|
:label="$t('ad_space_title')"
|
||||||
|
:hint="$t('ad_space_title_hint')"
|
||||||
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<q-input
|
||||||
|
class="q-mb-md"
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_ad_space"
|
||||||
|
type="text"
|
||||||
|
:label="$t('ad_slots')"
|
||||||
|
:hint="$t('ad_slots_hint')"
|
||||||
|
><q-tooltip>
|
||||||
|
format {url};{img-light};{img-dark},{url};{img-light};{img-dark}"
|
||||||
|
</q-tooltip>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-md q-my-md">
|
<div class="row q-col-gutter-md q-my-md">
|
||||||
<div class="col-12" v-text="$t('ui_default_theme')"></div>
|
<div class="col-12" v-text="$t('ui_default_theme')"></div>
|
||||||
@@ -221,12 +256,50 @@
|
|||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-sm-6 col-lg-3">
|
</div>
|
||||||
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
|
<div class="col-12 col-sm-6 col-lg-2">
|
||||||
<q-toggle
|
<q-toggle
|
||||||
type="bool"
|
type="bool"
|
||||||
v-model="formData.lnbits_default_gradient"
|
v-model="formData.lnbits_default_gradient"
|
||||||
color="primary"
|
color="primary"
|
||||||
label="Gradient Background"
|
:label="$t('gradient_background')"
|
||||||
|
>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-lg-2">
|
||||||
|
<q-toggle
|
||||||
|
type="bool"
|
||||||
|
v-model="formData.lnbits_default_dark"
|
||||||
|
color="primary"
|
||||||
|
:label="$t('toggle_darkmode')"
|
||||||
|
>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-lg-2">
|
||||||
|
<q-toggle
|
||||||
|
type="bool"
|
||||||
|
v-model="formData.lnbits_default_card_rounded"
|
||||||
|
color="primary"
|
||||||
|
:label="$t('rounded_ui')"
|
||||||
|
>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-lg-2">
|
||||||
|
<q-toggle
|
||||||
|
type="bool"
|
||||||
|
v-model="formData.lnbits_default_card_gradient"
|
||||||
|
color="primary"
|
||||||
|
:label="$t('card_gradient')"
|
||||||
|
>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-sm-6 col-lg-2">
|
||||||
|
<q-toggle
|
||||||
|
type="bool"
|
||||||
|
v-model="formData.lnbits_default_card_shadow"
|
||||||
|
color="primary"
|
||||||
|
:label="$t('card_shadow')"
|
||||||
>
|
>
|
||||||
</q-toggle>
|
</q-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
>
|
>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-md-6">
|
<div class="col-12 col-md-6">
|
||||||
<p><span v-text="$t('allowed_users')"></span></p>
|
<p><span v-text="$t('allowed_users')"></span></p>
|
||||||
@@ -57,7 +56,10 @@
|
|||||||
>
|
>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 q-mt-sm">
|
||||||
<q-item tag="label" v-ripple>
|
<q-item tag="label" v-ripple>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label v-text="$t('allow_creation_user')"></q-item-label>
|
<q-item-label v-text="$t('allow_creation_user')"></q-item-label>
|
||||||
@@ -76,8 +78,237 @@
|
|||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<br />
|
</div>
|
||||||
|
<div class="col-12 col-md-6 q-mt-sm">
|
||||||
|
<q-item tag="label" v-ripple>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label v-text="$t('require_user_activation')"></q-item-label>
|
||||||
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="$t('require_user_activation_desc')"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle
|
||||||
|
size="md"
|
||||||
|
v-model="formData.lnbits_require_user_activation"
|
||||||
|
checked-icon="check"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
<div v-if="formData.lnbits_require_user_activation" class="row">
|
||||||
|
<div class="col">
|
||||||
|
<q-list bordered class="rounded-borders">
|
||||||
|
<q-expansion-item header-class="text-primary text-bold">
|
||||||
|
<template v-slot:header>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="confirmation_number" size="md"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section> Invitation Code </q-item-section>
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-toggle
|
||||||
|
size="md"
|
||||||
|
:label="
|
||||||
|
formData.lnbits_user_activation_by_invitation_code
|
||||||
|
? $t('enabled')
|
||||||
|
: $t('disabled')
|
||||||
|
"
|
||||||
|
v-model="formData.lnbits_user_activation_by_invitation_code"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-card class="q-pb-xl">
|
||||||
|
<q-card-section>
|
||||||
|
<div
|
||||||
|
class="q-my-md q-pa-sm text-body2 text-grey-4 bg-grey-9 rounded-borders"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="info"
|
||||||
|
color="orange-4"
|
||||||
|
size="18px"
|
||||||
|
class="q-mr-xs"
|
||||||
|
></q-icon>
|
||||||
|
Users will need to provide a valid invitation code during
|
||||||
|
registration to activate their account.
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 col-md-6 q-pr-sm">
|
||||||
|
<p><span v-text="$t('reusable_activation_code')"></span></p>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formData.lnbits_register_reusable_activation_code"
|
||||||
|
:type="showReusableActivationCode ? 'text' : 'password'"
|
||||||
|
:label="$t('reusable_activation_code_label')"
|
||||||
|
:hint="$t('reusable_activation_code_hint')"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
@click="
|
||||||
|
showReusableActivationCode = !showReusableActivationCode
|
||||||
|
"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
:icon="
|
||||||
|
showReusableActivationCode
|
||||||
|
? 'visibility_off'
|
||||||
|
: 'visibility'
|
||||||
|
"
|
||||||
|
color="grey"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
@click="
|
||||||
|
utils.copyText(
|
||||||
|
formData.lnbits_register_reusable_activation_code
|
||||||
|
)
|
||||||
|
"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="content_copy"
|
||||||
|
color="grey"
|
||||||
|
></q-btn>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12 col-md-6">
|
||||||
|
<p><span v-text="$t('one_time_activation_code')"></span></p>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-model="formAddActivationCode"
|
||||||
|
@keydown.enter="addOneTimeActivationCode"
|
||||||
|
type="text"
|
||||||
|
:label="$t('one_time_activation_code_label')"
|
||||||
|
:hint="$t('one_time_activation_code_hint')"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
@click="addOneTimeActivationCode"
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
icon="add"
|
||||||
|
></q-btn>
|
||||||
|
</q-input>
|
||||||
|
<div>
|
||||||
|
<q-chip
|
||||||
|
v-for="code in formData.lnbits_register_one_time_activation_codes"
|
||||||
|
:key="code"
|
||||||
|
removable
|
||||||
|
@remove="removeOneTimeActivationCode(code)"
|
||||||
|
color="primary"
|
||||||
|
text-color="white"
|
||||||
|
:label="code"
|
||||||
|
class="ellipsis"
|
||||||
|
>
|
||||||
|
</q-chip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
|
||||||
|
<q-expansion-item header-class="text-primary text-bold">
|
||||||
|
<template v-slot:header>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar>
|
||||||
|
<q-icon name="bolt" size="md"></q-icon>
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section> Payment </q-item-section>
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-toggle
|
||||||
|
size="md"
|
||||||
|
disable
|
||||||
|
:label="
|
||||||
|
formData.lnbits_user_activation_by_payment
|
||||||
|
? $t('enabled')
|
||||||
|
: $t('disabled')
|
||||||
|
"
|
||||||
|
v-model="formData.lnbits_user_activation_by_payment"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-card class="q-pb-xl">
|
||||||
|
<q-card-section>
|
||||||
|
<div
|
||||||
|
class="q-my-md q-pa-sm text-body2 text-grey-4 bg-grey-9 rounded-borders"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="info"
|
||||||
|
color="orange-4"
|
||||||
|
size="18px"
|
||||||
|
class="q-mr-xs"
|
||||||
|
></q-icon>
|
||||||
|
Users will need to make a payment during registration to
|
||||||
|
activate their account.
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
<q-separator></q-separator>
|
||||||
|
<q-expansion-item header-class="text-primary text-bold">
|
||||||
|
<template v-slot:header>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-avatar>
|
||||||
|
<q-icon name="email"></q-icon>
|
||||||
|
</q-avatar>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
|
<q-item-section> Email </q-item-section>
|
||||||
|
|
||||||
|
<q-item-section side>
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-toggle
|
||||||
|
disable
|
||||||
|
size="md"
|
||||||
|
:label="
|
||||||
|
formData.lnbits_user_activation_by_email
|
||||||
|
? $t('enabled')
|
||||||
|
: $t('disabled')
|
||||||
|
"
|
||||||
|
v-model="formData.lnbits_user_activation_by_email"
|
||||||
|
color="green"
|
||||||
|
unchecked-icon="clear"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<q-card class="q-pb-xl">
|
||||||
|
<q-card-section>
|
||||||
|
<div
|
||||||
|
class="q-my-md q-pa-sm text-body2 text-grey-4 bg-grey-9 rounded-borders"
|
||||||
|
>
|
||||||
|
<q-icon
|
||||||
|
name="info"
|
||||||
|
color="orange-4"
|
||||||
|
size="18px"
|
||||||
|
class="q-mr-xs"
|
||||||
|
></q-icon>
|
||||||
|
Users will receive a confirmation email.
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</q-expansion-item>
|
||||||
|
</q-list>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template id="lnbits-footer">
|
<template id="lnbits-footer">
|
||||||
<q-footer
|
<q-footer
|
||||||
v-if="showFooter"
|
v-if="g.settings.showHomePageElements"
|
||||||
class="bg-transparent q-px-lg q-py-md"
|
class="bg-transparent q-px-lg q-py-md"
|
||||||
:class="{'text-dark': !$q.dark.isActive}"
|
:class="{'text-dark': !$q.dark.isActive}"
|
||||||
>
|
>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
<q-toolbar-title class="text-caption">
|
<q-toolbar-title class="text-caption">
|
||||||
<span v-text="title"></span>
|
<span v-text="title"></span>
|
||||||
<br />
|
<br />
|
||||||
<small v-text="$t('lnbits_version') + ': ' + version"></small>
|
<small v-text="version"></small>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<q-btn
|
<q-btn
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
<template id="lnbits-header">
|
<template id="lnbits-header">
|
||||||
<q-header bordered class="bg-marginal-bg">
|
<q-header bordered class="bg-marginal-bg">
|
||||||
<q-banner v-if="showVoidwallet" class="bg-warning text-white" dense>
|
<q-banner
|
||||||
|
v-if="g.settings.showVoidwallet"
|
||||||
|
class="bg-warning text-white"
|
||||||
|
dense
|
||||||
|
>
|
||||||
<template v-slot:avatar>
|
<template v-slot:avatar>
|
||||||
<q-icon name="warning" color="white" />
|
<q-icon name="warning" color="white" />
|
||||||
</template>
|
</template>
|
||||||
@@ -23,14 +27,13 @@
|
|||||||
></q-btn>
|
></q-btn>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
|
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
|
||||||
<q-img
|
<q-avatar v-if="g.settings.customLogo" height="30px">
|
||||||
v-if="customLogoUrl"
|
<img alt="Logo" :src="g.settings.customLogo" />
|
||||||
height="30px"
|
</q-avatar>
|
||||||
alt="Logo"
|
<span v-else-if="g.settings.siteTitle == 'LNbits'"
|
||||||
:src="customLogoUrl"
|
><strong>LN</strong>bits</span
|
||||||
></q-img>
|
>
|
||||||
<span v-else-if="!titleIsLnbits"><strong>LN</strong>bits</span>
|
<span v-else v-text="g.settings.siteTitle"></span>
|
||||||
<span v-else v-text="title"></span>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
<q-badge v-if="g.user && g.user.super_user">Super User</q-badge>
|
<q-badge v-if="g.user && g.user.super_user">Super User</q-badge>
|
||||||
<q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge>
|
<q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge>
|
||||||
@@ -38,31 +41,31 @@
|
|||||||
<q-badge
|
<q-badge
|
||||||
class="q-mr-md"
|
class="q-mr-md"
|
||||||
v-show="$q.screen.gt.sm"
|
v-show="$q.screen.gt.sm"
|
||||||
v-if="hasCustomBadge"
|
v-if="g.settings.customBadge"
|
||||||
:label="customBadge"
|
:label="g.settings.customBadge"
|
||||||
:color="customBadgeColor"
|
:color="g.settings.customBadgeColor"
|
||||||
>
|
>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
|
||||||
<q-badge
|
<q-badge
|
||||||
v-show="$q.screen.gt.sm"
|
v-show="$q.screen.gt.sm"
|
||||||
v-if="hasServiceFee"
|
v-if="g.user && g.settings.serviceFee > 0"
|
||||||
color="green"
|
color="green"
|
||||||
class="q-mr-md"
|
class="q-mr-md"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="hasServiceFeeMax"
|
v-if="g.user && g.settings.serviceFeeMax > 0"
|
||||||
v-text="
|
v-text="
|
||||||
$t('service_fee_max_badge', {
|
$t('service_fee_max_badge', {
|
||||||
amount: serviceFee,
|
amount: g.settings.serviceFee,
|
||||||
max: serviceFeeMax,
|
max: g.settings.serviceFeeMax,
|
||||||
denom: g.denomination
|
denom: g.denomination
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
></span>
|
></span>
|
||||||
<span
|
<span
|
||||||
v-else
|
v-else
|
||||||
v-text="$t('service_fee_badge', {amount: serviceFee})"
|
v-text="$t('service_fee_badge', {amount: g.settings.serviceFee})"
|
||||||
></span>
|
></span>
|
||||||
<q-tooltip><span v-text="$t('service_fee_tooltip')"></span></q-tooltip>
|
<q-tooltip><span v-text="$t('service_fee_tooltip')"></span></q-tooltip>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
@@ -71,8 +74,11 @@
|
|||||||
<span>OFFLINE</span>
|
<span>OFFLINE</span>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
|
||||||
<lnbits-language-dropdown></lnbits-language-dropdown>
|
<lnbits-language-dropdown
|
||||||
<q-btn-dropdown v-if="g.user" flat rounded size="sm" class="q-pl-sm">
|
@language-changed="handleLanguageChanged({locale: $event})"
|
||||||
|
></lnbits-language-dropdown>
|
||||||
|
|
||||||
|
<q-btn-dropdown v-if="g.user" flat rounded size="md" class="q-pl-sm">
|
||||||
<template v-slot:label>
|
<template v-slot:label>
|
||||||
<q-avatar
|
<q-avatar
|
||||||
v-if="g.user?.extra?.picture && g.user?.extra?.picture !== ''"
|
v-if="g.user?.extra?.picture && g.user?.extra?.picture !== ''"
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template id="lnbits-language-dropdown">
|
<template id="lnbits-language-dropdown">
|
||||||
<q-btn-dropdown dense flat rounded size="sm" icon="language" class="q-pl-md">
|
<q-btn-dropdown dense flat rounded size="md" class="q-pl-md">
|
||||||
|
<template v-slot:label>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label v-text="currentLanguage.display"></q-item-label>
|
||||||
|
<q-tooltip><span v-text="currentLanguage.label"></span></q-tooltip>
|
||||||
|
</q-item-section>
|
||||||
|
</template>
|
||||||
<q-list v-for="(lang, index) in langs" :key="index">
|
<q-list v-for="(lang, index) in langs" :key="index">
|
||||||
<q-item
|
<q-item
|
||||||
clickable
|
clickable
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -118,7 +118,7 @@
|
|||||||
</q-list>
|
</q-list>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="!HIDE_API"
|
v-if="!g.settings.hideApi"
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
@@ -152,7 +152,7 @@
|
|||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="!HIDE_API"
|
v-if="!g.settings.hideApi"
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="!HIDE_API"
|
v-if="!g.settings.hideApi"
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
@@ -244,7 +244,7 @@
|
|||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="!HIDE_API"
|
v-if="!g.settings.hideApi"
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
@@ -273,7 +273,7 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-expansion-item
|
<q-expansion-item
|
||||||
v-if="!HIDE_API"
|
v-if="!g.settings.hideApi"
|
||||||
group="api"
|
group="api"
|
||||||
dense
|
dense
|
||||||
expand-separator
|
expand-separator
|
||||||
|
|||||||
@@ -258,15 +258,20 @@
|
|||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
<div v-if="!g.user.email" class="row"></div>
|
|
||||||
<div v-if="!g.user.email" class="row">
|
<div v-if="!g.user.email" class="row">
|
||||||
{% if "google-auth" in LNBITS_AUTH_METHODS or
|
<div
|
||||||
"github-auth" in LNBITS_AUTH_METHODS %}
|
v-if="
|
||||||
<div class="col q-pa-sm text-h6">
|
'google-auth' in g.settings.authMethods ||
|
||||||
|
'github-auth' in g.settings.authMethods
|
||||||
|
"
|
||||||
|
class="col q-pa-sm text-h6"
|
||||||
|
>
|
||||||
<span v-text="$t('verify_email')"></span>:
|
<span v-text="$t('verify_email')"></span>:
|
||||||
</div>
|
</div>
|
||||||
{%endif%} {% if "google-auth" in LNBITS_AUTH_METHODS %}
|
<div
|
||||||
<div class="col q-pa-sm">
|
v-if="'google-auth' in g.settings.authMethods"
|
||||||
|
class="col q-pa-sm"
|
||||||
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
:href="`/api/v1/auth/google?user_id=${g.user.id}`"
|
:href="`/api/v1/auth/google?user_id=${g.user.id}`"
|
||||||
type="a"
|
type="a"
|
||||||
@@ -284,8 +289,10 @@
|
|||||||
<div>Google</div>
|
<div>Google</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
{%endif%} {% if "github-auth" in LNBITS_AUTH_METHODS %}
|
<div
|
||||||
<div class="col q-pa-sm">
|
v-if="'github-auth' in g.settings.authMethods"
|
||||||
|
class="col q-pa-sm"
|
||||||
|
>
|
||||||
<q-btn
|
<q-btn
|
||||||
:href="`/api/v1/auth/github?user_id=${g.user.id}`"
|
:href="`/api/v1/auth/github?user_id=${g.user.id}`"
|
||||||
type="a"
|
type="a"
|
||||||
@@ -303,7 +310,6 @@
|
|||||||
<div>GitHub</div>
|
<div>GitHub</div>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
{%endif%}
|
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
@@ -333,6 +339,16 @@
|
|||||||
class="q-mb-md"
|
class="q-mb-md"
|
||||||
>
|
>
|
||||||
</q-input>
|
</q-input>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-model="g.user.extra.visible_wallet_count"
|
||||||
|
:label="$t('visible_wallet_count')"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
type="number"
|
||||||
|
class="q-mb-md"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
<q-input
|
<q-input
|
||||||
v-model="g.user.external_id"
|
v-model="g.user.external_id"
|
||||||
:label="$t('external_id')"
|
:label="$t('external_id')"
|
||||||
@@ -380,22 +396,11 @@
|
|||||||
<span v-text="$t('language')"></span>
|
<span v-text="$t('language')"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<lnbits-language-dropdown />
|
<lnbits-language-dropdown
|
||||||
</div>
|
@language-changed="
|
||||||
</div>
|
siteCustomisationChanged({locale: $event})
|
||||||
<div class="row q-mb-md">
|
"
|
||||||
<div class="col-4">
|
/>
|
||||||
<span v-text="$t('visible_wallet_count')"></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-8">
|
|
||||||
<q-input
|
|
||||||
v-model="g.user.extra.visible_wallet_count"
|
|
||||||
:label="$t('visible_wallet_count')"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
type="number"
|
|
||||||
class="q-mb-md"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -407,7 +412,9 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
v-for="theme in themeOptions"
|
v-for="theme in themeOptions"
|
||||||
:key="theme.name"
|
:key="theme.name"
|
||||||
@click="g.themeChoice = theme.name"
|
@click="
|
||||||
|
siteCustomisationChanged({themeChoice: theme.name})
|
||||||
|
"
|
||||||
:color="theme.color"
|
:color="theme.color"
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
@@ -427,6 +434,9 @@
|
|||||||
<q-input
|
<q-input
|
||||||
v-model="g.bgimageChoice"
|
v-model="g.bgimageChoice"
|
||||||
:label="$t('background_image')"
|
:label="$t('background_image')"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({bgimageChoice: $event})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('background_image')"></span
|
><span v-text="$t('background_image')"></span
|
||||||
@@ -445,6 +455,9 @@
|
|||||||
round
|
round
|
||||||
icon="gradient"
|
icon="gradient"
|
||||||
v-model="g.gradientChoice"
|
v-model="g.gradientChoice"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({gradientChoice: $event})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('toggle_gradient')"></span
|
><span v-text="$t('toggle_gradient')"></span
|
||||||
@@ -452,6 +465,69 @@
|
|||||||
</q-toggle>
|
</q-toggle>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('rounded_ui')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-toggle
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="rounded_corner"
|
||||||
|
v-model="g.cardRoundedChoice"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({cardRoundedChoice: $event})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
><span v-text="$t('toggle_rounded_ui')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('card_gradient')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-toggle
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="gradient"
|
||||||
|
v-model="g.cardGradientChoice"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({cardGradientChoice: $event})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
><span v-text="$t('toggle_card_gradient')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<div class="col-4">
|
||||||
|
<span v-text="$t('card_shadow')"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-8">
|
||||||
|
<q-toggle
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="blur_on"
|
||||||
|
v-model="g.cardShadowChoice"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({cardShadowChoice: $event})
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<q-tooltip
|
||||||
|
><span v-text="$t('toggle_card_shadow')"></span
|
||||||
|
></q-tooltip>
|
||||||
|
</q-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
@@ -463,6 +539,9 @@
|
|||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
v-model="g.darkChoice"
|
v-model="g.darkChoice"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({darkChoice: $event})
|
||||||
|
"
|
||||||
:icon="$q.dark.isActive ? 'brightness_3' : 'wb_sunny'"
|
:icon="$q.dark.isActive ? 'brightness_3' : 'wb_sunny'"
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
@@ -481,6 +560,9 @@
|
|||||||
v-model="g.borderChoice"
|
v-model="g.borderChoice"
|
||||||
:options="borderOptions"
|
:options="borderOptions"
|
||||||
label="Borders"
|
label="Borders"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({borderChoice: $event})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('border_choices')"></span
|
><span v-text="$t('border_choices')"></span
|
||||||
@@ -495,7 +577,7 @@
|
|||||||
<div class="col-8">
|
<div class="col-8">
|
||||||
<lnbits-notifications-btn
|
<lnbits-notifications-btn
|
||||||
v-if="g.user"
|
v-if="g.user"
|
||||||
pubkey="{{ WEBPUSH_PUBKEY }}"
|
pubkey="g.settings.webpushPubkey"
|
||||||
></lnbits-notifications-btn>
|
></lnbits-notifications-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -508,6 +590,9 @@
|
|||||||
v-model="g.reactionChoice"
|
v-model="g.reactionChoice"
|
||||||
:options="reactionOptions"
|
:options="reactionOptions"
|
||||||
label="Reactions"
|
label="Reactions"
|
||||||
|
@update:model-value="
|
||||||
|
siteCustomisationChanged({reactionChoice: $event})
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('payment_reactions')"></span
|
><span v-text="$t('payment_reactions')"></span
|
||||||
@@ -515,6 +600,15 @@
|
|||||||
</q-select>
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<q-card-section>
|
||||||
|
<q-btn
|
||||||
|
@click="resetThemeDefaults"
|
||||||
|
:label="$t('reset_defaults')"
|
||||||
|
filled
|
||||||
|
color="primary"
|
||||||
|
class="float-right q-mb-md"
|
||||||
|
></q-btn>
|
||||||
|
</q-card-section>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="notifications">
|
<q-tab-panel name="notifications">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -525,7 +619,7 @@
|
|||||||
></span>
|
></span>
|
||||||
<br />
|
<br />
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="!LNBITS_NOSTR_CONFIGURED"
|
v-if="!g.settings.nostrConfigured"
|
||||||
v-text="$t('not_connected')"
|
v-text="$t('not_connected')"
|
||||||
></q-badge>
|
></q-badge>
|
||||||
</div>
|
</div>
|
||||||
@@ -545,7 +639,7 @@
|
|||||||
<span v-text="$t('notifications_chat_id')"></span>
|
<span v-text="$t('notifications_chat_id')"></span>
|
||||||
<br />
|
<br />
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="!LNBITS_TELEGRAM_CONFIGURED"
|
v-if="!g.settings.telegramConfigured"
|
||||||
v-text="$t('not_connected')"
|
v-text="$t('not_connected')"
|
||||||
></q-badge>
|
></q-badge>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
"
|
"
|
||||||
:clickable="!!reviewsUrl"
|
:clickable="!!reviewsUrl"
|
||||||
@click="openReviews(extension)"
|
@click="openReviews(extension)"
|
||||||
/>
|
></lnbits-extension-rating>
|
||||||
<q-btn-group size="xs" style="margin: 5px 0">
|
<q-btn-group size="xs" style="margin: 5px 0">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="extension.hasFreeRelease"
|
v-if="extension.hasFreeRelease"
|
||||||
|
|||||||
@@ -53,6 +53,19 @@
|
|||||||
@click="loginData.isPwdRepeat = !loginData.isPwdRepeat"
|
@click="loginData.isPwdRepeat = !loginData.isPwdRepeat"
|
||||||
/> </template
|
/> </template
|
||||||
></q-input>
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
filled
|
||||||
|
v-if="g.settings.hasFirstInstallToken"
|
||||||
|
v-model.trim="loginData.firstInstallToken"
|
||||||
|
:type="loginData.isPwd ? 'password' : 'text'"
|
||||||
|
:label="$t('first_install_token')"
|
||||||
|
><template v-slot:append>
|
||||||
|
<q-icon
|
||||||
|
:name="loginData.isPwd ? 'visibility_off' : 'visibility'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@click="loginData.isPwd = !loginData.isPwd"
|
||||||
|
/> </template
|
||||||
|
></q-input>
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="setPassword()"
|
@click="setPassword()"
|
||||||
unelevated
|
unelevated
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
<div class="home row justify-center items-center">
|
<div class="home row justify-center items-center">
|
||||||
<div
|
<div
|
||||||
class="full-width content-center"
|
class="full-width content-center"
|
||||||
:style="`max-width: ${hasCustomImage ? '850' : '600'}px; min-height: 55vh;`"
|
:style="`max-width: ${g.settings.customImage ? '850' : '600'}px; min-height: 55vh;`"
|
||||||
>
|
>
|
||||||
<div v-if="showHomepageElements" class="row q-mb-md">
|
<div v-if="g.settings.showHomepageElements" class="row q-mb-md">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<div>
|
<div>
|
||||||
<h5 v-text="siteTitle" class="q-my-none"></h5>
|
<h5 v-text="g.settings.siteTitle" class="q-my-none"></h5>
|
||||||
<template v-if="$q.screen.gt.sm">
|
<template v-if="$q.screen.gt.sm">
|
||||||
<h6 class="q-my-sm" v-text="siteTagline"></h6>
|
<h6 class="q-my-sm" v-text="g.settings.siteTagline"></h6>
|
||||||
<p class="q-my-sm" v-html="formatDescription"></p>
|
<p class="q-my-sm" v-html="formatDescription"></p>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,7 +31,10 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div
|
<div
|
||||||
class="col-12"
|
class="col-12"
|
||||||
:class="{'col-sm-7': hasCustomImage, 'col-lg-6': hasCustomImage}"
|
:class="{
|
||||||
|
'col-sm-7': g.settings.customImage,
|
||||||
|
'col-lg-6': g.settings.customImage
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<div v-if="showClaimLnurl">
|
<div v-if="showClaimLnurl">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -54,12 +57,13 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<username-password
|
<username-password
|
||||||
v-if="authMethod != 'user-id-only'"
|
v-if="authMethod != 'user-id-only'"
|
||||||
:allowed_new_users="allowRegister"
|
:allowed_new_users="g.settings.allowRegister"
|
||||||
:auth-methods="LNBITS_AUTH_METHODS"
|
:auth-methods="g.settings.authMethods"
|
||||||
:auth-action="authAction"
|
:auth-action="authAction"
|
||||||
v-model:user-name="username"
|
v-model:user-name="username"
|
||||||
v-model:password_1="password"
|
v-model:password_1="password"
|
||||||
v-model:password_2="passwordRepeat"
|
v-model:password_2="passwordRepeat"
|
||||||
|
v-model:invitation-code="invitationCode"
|
||||||
v-model:reset-key="reset_key"
|
v-model:reset-key="reset_key"
|
||||||
@login="login"
|
@login="login"
|
||||||
@register="register"
|
@register="register"
|
||||||
@@ -70,7 +74,7 @@
|
|||||||
v-if="authAction !== 'reset'"
|
v-if="authAction !== 'reset'"
|
||||||
>
|
>
|
||||||
<p
|
<p
|
||||||
v-if="authAction === 'login' && allowRegister"
|
v-if="authAction === 'login' && g.settings.allowRegister"
|
||||||
class="q-mb-none"
|
class="q-mb-none"
|
||||||
>
|
>
|
||||||
Not registered?
|
Not registered?
|
||||||
@@ -82,7 +86,9 @@
|
|||||||
>
|
>
|
||||||
</p>
|
</p>
|
||||||
<p
|
<p
|
||||||
v-else-if="authAction === 'login' && !allowRegister"
|
v-else-if="
|
||||||
|
authAction === 'login' && !g.settings.allowRegister
|
||||||
|
"
|
||||||
class="q-mb-none"
|
class="q-mb-none"
|
||||||
>
|
>
|
||||||
<span v-text="$t('new_user_not_allowed')"></span>
|
<span v-text="$t('new_user_not_allowed')"></span>
|
||||||
@@ -101,7 +107,7 @@
|
|||||||
</username-password>
|
</username-password>
|
||||||
<user-id-only
|
<user-id-only
|
||||||
v-if="authMethod == 'user-id-only'"
|
v-if="authMethod == 'user-id-only'"
|
||||||
:allowed_new_users="allowRegister"
|
:allowed_new_users="g.settings.allowRegister"
|
||||||
v-model:usr="usr"
|
v-model:usr="usr"
|
||||||
v-model:wallet="walletName"
|
v-model:wallet="walletName"
|
||||||
:auth-action="authAction"
|
:auth-action="authAction"
|
||||||
@@ -114,16 +120,20 @@
|
|||||||
</user-id-only>
|
</user-id-only>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasCustomImage" class="col-sm-5 col-lg-6 gt-xs">
|
<div v-if="g.settings.customImage" class="col-sm-5 col-lg-6 gt-xs">
|
||||||
<div class="full-height flex flex-center q-pa-lg">
|
<div class="full-height flex flex-center q-pa-lg">
|
||||||
<q-img :src="hasCustomImage" :ratio="1" width="250px"></q-img>
|
<q-img
|
||||||
|
:src="g.settings.customImage"
|
||||||
|
:ratio="1"
|
||||||
|
width="250px"
|
||||||
|
></q-img>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="showHomepageElements">
|
<div v-if="g.settings.showHomepageElements">
|
||||||
<div class="flex flex-center q-gutter-md q-py-md">
|
<div class="flex flex-center q-gutter-md q-py-md">
|
||||||
<q-btn
|
<q-btn
|
||||||
outline
|
outline
|
||||||
@@ -148,10 +158,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="adsEnabled"
|
v-if="g.settings.showAdSpace"
|
||||||
class="justify-center col-10 q-my-lg row q-col-gutter-sm"
|
class="justify-center col-10 q-my-lg row q-col-gutter-sm"
|
||||||
>
|
>
|
||||||
<a :href="ad[0]" v-for="ad in g.ads" class="lnbits-ad col-12 col-md-4">
|
<a
|
||||||
|
:href="ad[0]"
|
||||||
|
v-for="ad in g.settings.adSpace"
|
||||||
|
class="lnbits-ad col-12 col-md-4"
|
||||||
|
>
|
||||||
<q-img v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
<q-img v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
||||||
<q-img v-else :src="ad[2]"></q-img>
|
<q-img v-else :src="ad[2]"></q-img>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -218,6 +218,26 @@
|
|||||||
:label="$t('update_account')"
|
:label="$t('update_account')"
|
||||||
class="q-ml-md"
|
class="q-ml-md"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="activeUser.data.id"
|
||||||
|
outline
|
||||||
|
color="primary"
|
||||||
|
class="q-ml-md"
|
||||||
|
>
|
||||||
|
<q-toggle
|
||||||
|
size="xs"
|
||||||
|
color="secondary"
|
||||||
|
v-model="activeUser.data.admin"
|
||||||
|
@update:model-value="toggleAdmin(activeUser.data.id)"
|
||||||
|
:label="
|
||||||
|
activeUser.data.admin
|
||||||
|
? $t('revoke_admin')
|
||||||
|
: $t('make_user_admin')
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</q-toggle>
|
||||||
|
</q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-else
|
v-else
|
||||||
@click="createUser()"
|
@click="createUser()"
|
||||||
@@ -590,9 +610,9 @@
|
|||||||
<q-btn
|
<q-btn
|
||||||
@click="showAccountPage(props.row.id)"
|
@click="showAccountPage(props.row.id)"
|
||||||
round
|
round
|
||||||
icon="edit"
|
:icon="props.row.is_admin ? 'admin_panel_settings' : 'edit'"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="secondary"
|
:color="props.row.is_admin ? 'primary' : 'secondary'"
|
||||||
class="q-ml-xs"
|
class="q-ml-xs"
|
||||||
>
|
>
|
||||||
<q-tooltip>
|
<q-tooltip>
|
||||||
@@ -605,10 +625,18 @@
|
|||||||
size="xs"
|
size="xs"
|
||||||
v-if="!props.row.is_super_user"
|
v-if="!props.row.is_super_user"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
v-model="props.row.is_admin"
|
v-model="props.row.activated"
|
||||||
@update:model-value="toggleAdmin(props.row.id)"
|
@update:model-value="toggleUserActivated(props.row.id)"
|
||||||
>
|
>
|
||||||
<q-tooltip>Toggle Admin</q-tooltip>
|
<q-tooltip
|
||||||
|
><span
|
||||||
|
v-text="
|
||||||
|
props.row.activated
|
||||||
|
? $t('deactivate')
|
||||||
|
: $t('activate')
|
||||||
|
"
|
||||||
|
></span
|
||||||
|
></q-tooltip>
|
||||||
</q-toggle>
|
</q-toggle>
|
||||||
<q-btn
|
<q-btn
|
||||||
round
|
round
|
||||||
|
|||||||
@@ -171,6 +171,19 @@
|
|||||||
><span v-text="$t('camera_tooltip')"></span
|
><span v-text="$t('camera_tooltip')"></span
|
||||||
></q-tooltip>
|
></q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
|
<div
|
||||||
|
v-if="g.settings.walletFeaturedButtonUrl"
|
||||||
|
class="float-right q-mt-sm q-ml-sm"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
:label="g.settings.walletFeaturedButtonLabel"
|
||||||
|
:icon="g.settings.walletFeaturedButtonIcon || undefined"
|
||||||
|
size="sm"
|
||||||
|
:to="g.settings.walletFeaturedButtonUrl"
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
<lnbits-update-balance
|
<lnbits-update-balance
|
||||||
v-if="$q.screen.gt.md"
|
v-if="$q.screen.gt.md"
|
||||||
:wallet_id="this.g.wallet.id"
|
:wallet_id="this.g.wallet.id"
|
||||||
@@ -191,7 +204,7 @@
|
|||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="!g.mobileSimple || !$q.screen.lt.md"
|
v-show="!g.mobileSimple || !$q.screen.lt.md"
|
||||||
class="col-12 col-md-5 q-gutter-y-md"
|
class="col-12 col-md-5 q-gutter-y-md"
|
||||||
>
|
>
|
||||||
<lnbits-wallet-extra
|
<lnbits-wallet-extra
|
||||||
@@ -199,10 +212,10 @@
|
|||||||
@send-lnurl="handleSendLnurl"
|
@send-lnurl="handleSendLnurl"
|
||||||
:chart-config="chartConfig"
|
:chart-config="chartConfig"
|
||||||
></lnbits-wallet-extra>
|
></lnbits-wallet-extra>
|
||||||
<q-card class="lnbits-wallet-ads" v-if="AD_SPACE_ENABLED">
|
<q-card class="lnbits-wallet-ads" v-if="g.settings.showAdSpace">
|
||||||
<q-card-section class="text-subtitle1">
|
<q-card-section class="text-subtitle1">
|
||||||
<span v-text="AD_SPACE_TITLE"></span>
|
<span v-text="g.settings.adSpaceTitle"></span>
|
||||||
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.ads">
|
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.settings.adSpace">
|
||||||
<q-img class="q-mb-xs" v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
<q-img class="q-mb-xs" v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
||||||
<q-img class="q-mb-xs" v-else :src="ad[2]"></q-img>
|
<q-img class="q-mb-xs" v-else :src="ad[2]"></q-img>
|
||||||
</a>
|
</a>
|
||||||
@@ -276,7 +289,7 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<q-input
|
<q-input
|
||||||
v-if="has_holdinvoice"
|
v-if="g.settings.hasHoldinvoice"
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model="receive.data.payment_hash"
|
v-model="receive.data.payment_hash"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user