Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0ff997ec1 | ||
|
|
eb55170905 | ||
|
|
dc466ae738 | ||
|
|
dd5fe8b7a2 | ||
|
|
5196ee6a15 | ||
|
|
3498cfb5b8 | ||
|
|
30ffe437a0 | ||
|
|
574641d432 | ||
|
|
90d14629d9 | ||
|
|
3be06269c2 | ||
|
|
d7723696d8 | ||
|
|
91f7b7730d | ||
|
|
b2aaf97cf3 |
@@ -6,10 +6,6 @@
|
|||||||
# 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
|
||||||
|
|||||||
@@ -12,6 +12,13 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
uses: ./.github/workflows/lint.yml
|
uses: ./.github/workflows/lint.yml
|
||||||
|
|
||||||
|
test-frontend:
|
||||||
|
needs: [ lint ]
|
||||||
|
uses: ./.github/workflows/make.yml
|
||||||
|
with:
|
||||||
|
make: test-frontend
|
||||||
|
npm: true
|
||||||
|
|
||||||
test-api:
|
test-api:
|
||||||
needs: [ lint ]
|
needs: [ lint ]
|
||||||
strategy:
|
strategy:
|
||||||
|
|||||||
@@ -44,19 +44,15 @@ jobs:
|
|||||||
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: Set up Python 3.10
|
- name: Build and publish to pypi
|
||||||
uses: actions/setup-python@v5
|
uses: JRubics/poetry-publish@v1.15
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||||
- 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 ]
|
||||||
|
|||||||
@@ -56,19 +56,15 @@ jobs:
|
|||||||
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: Set up Python 3.10
|
- name: Build and publish to pypi
|
||||||
uses: actions/setup-python@v5
|
uses: JRubics/poetry-publish@v1.15
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||||
- 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 ]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
name: tests
|
name: tests pytest
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ dev:
|
|||||||
docker:
|
docker:
|
||||||
docker build -t lnbits/lnbits .
|
docker build -t lnbits/lnbits .
|
||||||
|
|
||||||
|
test-frontend:
|
||||||
|
npm install
|
||||||
|
npm test
|
||||||
|
|
||||||
test-wallets:
|
test-wallets:
|
||||||
LNBITS_DATA_FOLDER="./tests/data" \
|
LNBITS_DATA_FOLDER="./tests/data" \
|
||||||
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
LNBITS_BACKEND_WALLET_CLASS="FakeWallet" \
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
from datetime import datetime, timezone
|
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -305,16 +304,8 @@ async def update_payment_checking_id(
|
|||||||
checking_id: str, new_checking_id: str, conn: Connection | None = None
|
checking_id: str, new_checking_id: str, conn: Connection | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
"UPDATE apipayments SET checking_id = :new_id WHERE checking_id = :old_id",
|
||||||
UPDATE apipayments
|
{"new_id": new_checking_id, "old_id": checking_id},
|
||||||
SET checking_id = :new_id, updated_at = {db.timestamp_placeholder('now')}
|
|
||||||
WHERE checking_id = :old_id
|
|
||||||
""", # noqa: S608
|
|
||||||
{
|
|
||||||
"new_id": new_checking_id,
|
|
||||||
"old_id": checking_id,
|
|
||||||
"now": int(time()),
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -323,7 +314,6 @@ async def update_payment(
|
|||||||
new_checking_id: str | None = None,
|
new_checking_id: str | None = None,
|
||||||
conn: Connection | None = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
payment.updated_at = datetime.now(timezone.utc)
|
|
||||||
await (conn or db).update(
|
await (conn or db).update(
|
||||||
"apipayments", payment, "WHERE checking_id = :checking_id"
|
"apipayments", payment, "WHERE checking_id = :checking_id"
|
||||||
)
|
)
|
||||||
|
|||||||
+21
-76
@@ -4,12 +4,10 @@ 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 clear_wallet_cache, create_wallet, get_wallets
|
from lnbits.core.crud.wallets import 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,
|
||||||
@@ -43,7 +41,6 @@ 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(
|
||||||
@@ -72,7 +69,6 @@ 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,
|
||||||
@@ -97,18 +93,12 @@ async def get_accounts(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_account(
|
async def get_account(user_id: str, conn: Connection | None = None) -> Account | None:
|
||||||
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",
|
||||||
SELECT * FROM accounts
|
{"id": user_id},
|
||||||
WHERE id = :id AND (activated = true OR activated = :activated)
|
|
||||||
""",
|
|
||||||
{"id": user_id, "activated": active_only},
|
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,79 +124,55 @@ async def delete_accounts_no_wallets(
|
|||||||
|
|
||||||
|
|
||||||
async def get_account_by_username(
|
async def get_account_by_username(
|
||||||
username: str, active_only: bool = True, conn: Connection | None = None
|
username: str, 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",
|
||||||
SELECT * FROM accounts
|
{"username": username.lower()},
|
||||||
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, active_only: bool = True, conn: Connection | None = None
|
pubkey: str, 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",
|
||||||
SELECT * FROM accounts
|
{"pubkey": pubkey.lower()},
|
||||||
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, active_only: bool = True, conn: Connection | None = None
|
email: str, 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",
|
||||||
SELECT * FROM accounts
|
{"email": email.lower()},
|
||||||
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,
|
username_or_email: str, conn: Connection | None = None
|
||||||
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
|
WHERE LOWER(email) = :value or LOWER(username) = :value
|
||||||
(LOWER(email) = :value or LOWER(username) = :value)
|
|
||||||
AND (activated = true OR activated = :activated)
|
|
||||||
""",
|
""",
|
||||||
{"value": username_or_email.lower(), "activated": active_only},
|
{"value": username_or_email.lower()},
|
||||||
Account,
|
Account,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def get_user(
|
async def get_user(user_id: str, conn: Connection | None = None) -> User | None:
|
||||||
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, active_only, conn=conn)
|
account = await get_account(user_id, 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)
|
||||||
@@ -225,7 +191,6 @@ 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,
|
||||||
@@ -239,7 +204,6 @@ 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 {},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -251,31 +215,12 @@ async def update_user_access_control_list(
|
|||||||
|
|
||||||
|
|
||||||
async def get_user_access_control_lists(
|
async def get_user_access_control_lists(
|
||||||
user_id: str, active_only: bool = True, conn: Connection | None = None
|
user_id: str, 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",
|
||||||
SELECT id, access_control_list FROM accounts
|
{"id": user_id},
|
||||||
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)
|
|
||||||
|
|||||||
@@ -45,13 +45,17 @@ async def update_wallet(
|
|||||||
|
|
||||||
|
|
||||||
async def delete_wallet(
|
async def delete_wallet(
|
||||||
|
*,
|
||||||
user_id: str,
|
user_id: str,
|
||||||
wallet_id: str,
|
wallet_id: str,
|
||||||
deleted: bool = True,
|
deleted: bool = True,
|
||||||
conn: Connection | None = None,
|
conn: Connection | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
clear_wallet_id_cache(wallet_id)
|
|
||||||
now = int(time())
|
now = int(time())
|
||||||
|
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
||||||
|
if cached_wallet:
|
||||||
|
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
||||||
|
cache.pop(f"auth:x-api-key:{cached_wallet.inkey}")
|
||||||
|
|
||||||
await (conn or db).execute(
|
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)
|
||||||
@@ -65,7 +69,6 @@ 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_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 +78,6 @@ 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_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,14 +228,11 @@ async def get_wallet_for_key(
|
|||||||
) -> Wallet | None:
|
) -> Wallet | None:
|
||||||
wallet = await (conn or db).fetchone(
|
wallet = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT wallets.*, COALESCE((
|
SELECT *, 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
|
||||||
INNER JOIN accounts ON wallets.user = accounts.id
|
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
|
||||||
WHERE (adminkey = :key OR inkey = :key)
|
|
||||||
AND deleted = false
|
|
||||||
AND accounts.activated = true
|
|
||||||
""",
|
""",
|
||||||
{"key": key},
|
{"key": key},
|
||||||
Wallet,
|
Wallet,
|
||||||
@@ -253,11 +252,8 @@ async def get_base_wallet_for_key(
|
|||||||
) -> BaseWallet | None:
|
) -> BaseWallet | None:
|
||||||
wallet = await (conn or db).fetchone(
|
wallet = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT wallets.id, "user", wallet_type, adminkey, inkey FROM wallets
|
SELECT id, "user", wallet_type, adminkey, inkey FROM wallets
|
||||||
INNER JOIN accounts ON wallets.user = accounts.id
|
WHERE (adminkey = :key OR inkey = :key) AND deleted = false
|
||||||
WHERE (adminkey = :key OR inkey = :key)
|
|
||||||
AND deleted = false
|
|
||||||
AND accounts.activated = true
|
|
||||||
""",
|
""",
|
||||||
{"key": key},
|
{"key": key},
|
||||||
BaseWallet,
|
BaseWallet,
|
||||||
@@ -298,16 +294,3 @@ async def get_total_balance(conn: Connection | None = None):
|
|||||||
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
result = await (conn or db).execute("SELECT SUM(balance) as balance FROM balances")
|
||||||
row = result.mappings().first()
|
row = result.mappings().first()
|
||||||
return row.get("balance", 0) or 0
|
return row.get("balance", 0) or 0
|
||||||
|
|
||||||
|
|
||||||
def clear_wallet_id_cache(wallet_id: str):
|
|
||||||
cached_wallet: BaseWallet | None = cache.pop(f"auth:wallet:{wallet_id}")
|
|
||||||
if cached_wallet:
|
|
||||||
cache.pop(f"auth:x-api-key:{cached_wallet.adminkey}")
|
|
||||||
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,19 +848,3 @@ 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")
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from typing import Any
|
|||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from lnbits.helpers import (
|
from lnbits.helpers import (
|
||||||
download_url,
|
download_url,
|
||||||
@@ -269,30 +269,10 @@ class ExtensionRelease(BaseModel):
|
|||||||
async def get_github_releases(cls, org: str, repo: str) -> list[ExtensionRelease]:
|
async def get_github_releases(cls, org: str, repo: str) -> list[ExtensionRelease]:
|
||||||
try:
|
try:
|
||||||
github_releases = await cls.fetch_github_releases(org, repo)
|
github_releases = await cls.fetch_github_releases(org, repo)
|
||||||
extension_releases = [
|
return [
|
||||||
ExtensionRelease.from_github_release(f"{org}/{repo}", r)
|
ExtensionRelease.from_github_release(f"{org}/{repo}", r)
|
||||||
for r in github_releases
|
for r in github_releases
|
||||||
]
|
]
|
||||||
for release in extension_releases:
|
|
||||||
if not release.details_link:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
config = await ExtensionConfig.fetch_github_release_config(
|
|
||||||
org, repo, release.version
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
config = None
|
|
||||||
if not config:
|
|
||||||
continue
|
|
||||||
|
|
||||||
release.min_lnbits_version = config.min_lnbits_version
|
|
||||||
release.max_lnbits_version = config.max_lnbits_version
|
|
||||||
release.is_version_compatible = config.is_version_compatible()
|
|
||||||
|
|
||||||
release.icon = icon_to_github_url(f"{org}/{repo}", config.tile)
|
|
||||||
|
|
||||||
return extension_releases
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(e)
|
logger.warning(e)
|
||||||
return []
|
return []
|
||||||
@@ -797,32 +777,6 @@ class ExtensionDetailsRequest(BaseModel):
|
|||||||
version: str
|
version: str
|
||||||
|
|
||||||
|
|
||||||
class ExtensionReviewsStatus(BaseModel):
|
|
||||||
tag: str
|
|
||||||
avg_rating: float
|
|
||||||
review_count: int
|
|
||||||
|
|
||||||
|
|
||||||
class CreateExtensionReview(BaseModel):
|
|
||||||
tag: str
|
|
||||||
name: str | None = Field(None)
|
|
||||||
rating: int = Field(..., ge=0, le=1000)
|
|
||||||
comment: str | None = Field(None)
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionReviewPaymentRequest(BaseModel):
|
|
||||||
payment_hash: str
|
|
||||||
payment_request: str
|
|
||||||
|
|
||||||
|
|
||||||
class ExtensionReview(BaseModel):
|
|
||||||
id: str
|
|
||||||
name: str | None = Field(default=None)
|
|
||||||
tag: str | None = Field(default=None)
|
|
||||||
rating: int = Field(default=0, ge=0, le=1000)
|
|
||||||
comment: str | None = Field(default=None)
|
|
||||||
|
|
||||||
|
|
||||||
async def github_api_get(url: str, error_msg: str | None) -> Any:
|
async def github_api_get(url: str, error_msg: str | None) -> Any:
|
||||||
headers = {"User-Agent": settings.user_agent}
|
headers = {"User-Agent": settings.user_agent}
|
||||||
if settings.lnbits_ext_github_token:
|
if settings.lnbits_ext_github_token:
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import random
|
|
||||||
import uuid
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
@@ -57,29 +55,6 @@ class DataField(BaseModel):
|
|||||||
field_type += ' = "sat"'
|
field_type += ' = "sat"'
|
||||||
return f"{field_name}: {field_type}"
|
return f"{field_name}: {field_type}"
|
||||||
|
|
||||||
def field_to_random_value(self) -> str:
|
|
||||||
field_name = camel_to_snake(self.name)
|
|
||||||
field_value: Any = f'"{self.type}"'
|
|
||||||
if self.type == "json":
|
|
||||||
field_value = '"{}"'
|
|
||||||
elif self.type == "wallet":
|
|
||||||
field_value = f'"{uuid.uuid4()}"'
|
|
||||||
elif self.type == "currency":
|
|
||||||
field_value = '"sat"'
|
|
||||||
elif self.type in ["str", "text"]:
|
|
||||||
field_value = f'"{field_name}_{urlsafe_short_hash()}"'
|
|
||||||
elif self.type == "int":
|
|
||||||
field_value = random.randint(1, 100) # noqa: S311
|
|
||||||
elif self.type == "float":
|
|
||||||
field_value = random.uniform(1.0, 100.0) # noqa: S311
|
|
||||||
elif self.type == "bool":
|
|
||||||
field_value = random.choice([True, False]) # noqa: S311
|
|
||||||
elif self.type == "datetime":
|
|
||||||
random_days = random.randint(-30, 30) # noqa: S311
|
|
||||||
random_date = datetime.now(timezone.utc) - timedelta(days=random_days)
|
|
||||||
field_value = f"""datetime.fromisoformat("{random_date.isoformat()}")"""
|
|
||||||
return f"{field_name} = {field_value},"
|
|
||||||
|
|
||||||
def field_to_js(self) -> str:
|
def field_to_js(self) -> str:
|
||||||
field_name = camel_to_snake(self.name)
|
field_name = camel_to_snake(self.name)
|
||||||
default_value = "null"
|
default_value = "null"
|
||||||
|
|||||||
@@ -6,16 +6,23 @@ 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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,23 +130,59 @@ 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:
|
||||||
logger.warning("payment.check_status() is deprecated.")
|
if self.is_internal:
|
||||||
from lnbits.core.services.payments import check_payment_status
|
if self.success:
|
||||||
|
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:
|
||||||
logger.warning("payment.check_fiat_status() is deprecated.")
|
if not self.is_internal:
|
||||||
from lnbits.core.services.fiat_providers import check_fiat_status
|
return FiatPaymentPendingStatus()
|
||||||
|
if self.success:
|
||||||
|
return FiatPaymentSuccessStatus()
|
||||||
|
if self.failed:
|
||||||
|
return FiatPaymentFailedStatus()
|
||||||
|
|
||||||
return await check_fiat_status(self, skip_internal_payment_notifications)
|
if not self.fiat_provider:
|
||||||
|
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,14 +181,12 @@ 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))
|
||||||
@@ -242,7 +240,6 @@ 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
|
||||||
@@ -278,7 +275,6 @@ 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
|
||||||
@@ -292,7 +288,6 @@ 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]:
|
||||||
@@ -318,7 +313,6 @@ 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, min_length=1, max_length=256)
|
|
||||||
|
|
||||||
|
|
||||||
class CreateUser(BaseModel):
|
class CreateUser(BaseModel):
|
||||||
@@ -362,7 +356,6 @@ 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,4 +1,3 @@
|
|||||||
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,
|
||||||
@@ -8,7 +7,6 @@ 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,
|
||||||
@@ -42,8 +40,6 @@ __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",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import io
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import UploadFile
|
from fastapi import UploadFile
|
||||||
from loguru import logger
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from lnbits.core.crud.assets import create_asset, get_user_assets_count
|
from lnbits.core.crud.assets import create_asset, get_user_assets_count
|
||||||
@@ -30,7 +29,17 @@ async def create_user_asset(user_id: str, file: UploadFile, is_public: bool) ->
|
|||||||
f"File limit of {settings.lnbits_max_asset_size_mb}MB exceeded."
|
f"File limit of {settings.lnbits_max_asset_size_mb}MB exceeded."
|
||||||
)
|
)
|
||||||
|
|
||||||
thumb_buffer = thumbnail_from_bytes(contents)
|
image = Image.open(io.BytesIO(contents))
|
||||||
|
|
||||||
|
thumbnail_width = min(256, settings.lnbits_asset_thumbnail_width)
|
||||||
|
thumbnail_height = min(256, settings.lnbits_asset_thumbnail_height)
|
||||||
|
image.thumbnail((thumbnail_width, thumbnail_height))
|
||||||
|
|
||||||
|
# Save thumbnail to an in-memory buffer
|
||||||
|
thumb_buffer = io.BytesIO()
|
||||||
|
thumbnail_format = settings.lnbits_asset_thumbnail_format or "PNG"
|
||||||
|
image.save(thumb_buffer, format=thumbnail_format)
|
||||||
|
thumb_buffer.seek(0)
|
||||||
|
|
||||||
asset = Asset(
|
asset = Asset(
|
||||||
id=uuid4().hex,
|
id=uuid4().hex,
|
||||||
@@ -39,32 +48,9 @@ async def create_user_asset(user_id: str, file: UploadFile, is_public: bool) ->
|
|||||||
is_public=is_public,
|
is_public=is_public,
|
||||||
name=file.filename or "unnamed",
|
name=file.filename or "unnamed",
|
||||||
size_bytes=len(contents),
|
size_bytes=len(contents),
|
||||||
thumbnail_base64=(
|
thumbnail_base64=base64.b64encode(thumb_buffer.getvalue()).decode("utf-8"),
|
||||||
base64.b64encode(thumb_buffer.getvalue()).decode("utf-8")
|
|
||||||
if thumb_buffer
|
|
||||||
else None
|
|
||||||
),
|
|
||||||
data=contents,
|
data=contents,
|
||||||
)
|
)
|
||||||
|
|
||||||
await create_asset(asset)
|
await create_asset(asset)
|
||||||
return asset
|
return asset
|
||||||
|
|
||||||
|
|
||||||
def thumbnail_from_bytes(contents: bytes) -> io.BytesIO | None:
|
|
||||||
try:
|
|
||||||
image = Image.open(io.BytesIO(contents))
|
|
||||||
|
|
||||||
thumbnail_width = min(256, settings.lnbits_asset_thumbnail_width)
|
|
||||||
thumbnail_height = min(256, settings.lnbits_asset_thumbnail_height)
|
|
||||||
image.thumbnail((thumbnail_width, thumbnail_height))
|
|
||||||
|
|
||||||
# Save thumbnail to an in-memory buffer
|
|
||||||
thumb_buffer = io.BytesIO()
|
|
||||||
thumbnail_format = settings.lnbits_asset_thumbnail_format or "PNG"
|
|
||||||
image.save(thumb_buffer, format=thumbnail_format)
|
|
||||||
thumb_buffer.seek(0)
|
|
||||||
return thumb_buffer
|
|
||||||
except Exception as exc:
|
|
||||||
logger.warning(f"Failed to create thumbnail: {exc}")
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ from lnbits.helpers import (
|
|||||||
camel_to_words,
|
camel_to_words,
|
||||||
download_url,
|
download_url,
|
||||||
lowercase_first_letter,
|
lowercase_first_letter,
|
||||||
snake_to_camel,
|
|
||||||
)
|
)
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
@@ -196,29 +195,13 @@ def _copy_ext_stub_to_build_dir(
|
|||||||
ext_build_dir = Path(working_dir, new_ext_id, working_dir_name, new_ext_id)
|
ext_build_dir = Path(working_dir, new_ext_id, working_dir_name, new_ext_id)
|
||||||
shutil.rmtree(ext_build_dir, True)
|
shutil.rmtree(ext_build_dir, True)
|
||||||
|
|
||||||
shutil.copytree(
|
shutil.copytree(ext_stub_cache_dir, ext_build_dir)
|
||||||
ext_stub_cache_dir,
|
|
||||||
ext_build_dir,
|
|
||||||
ignore=shutil.ignore_patterns(
|
|
||||||
"__pycache__",
|
|
||||||
".git",
|
|
||||||
".env",
|
|
||||||
".venv",
|
|
||||||
"*.env",
|
|
||||||
"*.log",
|
|
||||||
"node_modules",
|
|
||||||
".mypy_cache",
|
|
||||||
".pytest_cache",
|
|
||||||
".ruff_cache",
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return ext_build_dir
|
return ext_build_dir
|
||||||
|
|
||||||
|
|
||||||
def _replace_jinja_placeholders(data: ExtensionData, ext_stub_dir: Path) -> None:
|
def _replace_jinja_placeholders(data: ExtensionData, ext_stub_dir: Path) -> None:
|
||||||
parsed_data = _parse_extension_data(data)
|
parsed_data = _parse_extension_data(data)
|
||||||
test_files = [f"tests/{p.name}" for p in Path(ext_stub_dir, "tests").glob("*.py")]
|
for py_file in py_files:
|
||||||
for py_file in py_files + test_files:
|
|
||||||
template_path = Path(ext_stub_dir, py_file).as_posix()
|
template_path = Path(ext_stub_dir, py_file).as_posix()
|
||||||
rederer = _render_file(template_path, parsed_data)
|
rederer = _render_file(template_path, parsed_data)
|
||||||
with open(template_path, "w", encoding="utf-8") as f:
|
with open(template_path, "w", encoding="utf-8") as f:
|
||||||
@@ -226,7 +209,7 @@ def _replace_jinja_placeholders(data: ExtensionData, ext_stub_dir: Path) -> None
|
|||||||
|
|
||||||
_remove_lines_with_string(template_path, remove_line_marker)
|
_remove_lines_with_string(template_path, remove_line_marker)
|
||||||
|
|
||||||
template_path = Path(ext_stub_dir, "static", "index.js").as_posix()
|
template_path = Path(ext_stub_dir, "static", "js", "index.js").as_posix()
|
||||||
rederer = _render_file(
|
rederer = _render_file(
|
||||||
template_path, {"preview": data.preview_action, **parsed_data}
|
template_path, {"preview": data.preview_action, **parsed_data}
|
||||||
)
|
)
|
||||||
@@ -251,7 +234,9 @@ def _replace_jinja_placeholders(data: ExtensionData, ext_stub_dir: Path) -> None
|
|||||||
"settingsFormDialog.data",
|
"settingsFormDialog.data",
|
||||||
ext_stub_dir,
|
ext_stub_dir,
|
||||||
)
|
)
|
||||||
template_path = Path(ext_stub_dir, "static", "index.vue").as_posix()
|
template_path = Path(
|
||||||
|
ext_stub_dir, "templates", "extension_builder_stub", "index.html"
|
||||||
|
).as_posix()
|
||||||
rederer = _render_file(
|
rederer = _render_file(
|
||||||
template_path,
|
template_path,
|
||||||
{
|
{
|
||||||
@@ -278,12 +263,12 @@ def _replace_jinja_placeholders(data: ExtensionData, ext_stub_dir: Path) -> None
|
|||||||
"publicClientData",
|
"publicClientData",
|
||||||
ext_stub_dir,
|
ext_stub_dir,
|
||||||
)
|
)
|
||||||
public_template_path = Path(ext_stub_dir, "static", "public_page.vue")
|
public_template_path = Path(
|
||||||
public_component_path = Path(ext_stub_dir, "static", "public_page.js")
|
ext_stub_dir, "templates", "extension_builder_stub", "public_page.html"
|
||||||
|
)
|
||||||
template_path = public_template_path.as_posix()
|
template_path = public_template_path.as_posix()
|
||||||
if not data.public_page.has_public_page:
|
if not data.public_page.has_public_page:
|
||||||
public_template_path.unlink(missing_ok=True)
|
public_template_path.unlink(missing_ok=True)
|
||||||
public_component_path.unlink(missing_ok=True)
|
|
||||||
else:
|
else:
|
||||||
rederer = _render_file(
|
rederer = _render_file(
|
||||||
template_path,
|
template_path,
|
||||||
@@ -323,10 +308,8 @@ def zip_directory(source_dir, zip_path):
|
|||||||
|
|
||||||
def _rename_extension_builder_stub(data: ExtensionData, extension_dir: Path) -> None:
|
def _rename_extension_builder_stub(data: ExtensionData, extension_dir: Path) -> None:
|
||||||
extension_dir_path = extension_dir.as_posix()
|
extension_dir_path = extension_dir.as_posix()
|
||||||
# the order of fields is important, do not chage
|
|
||||||
rename_values = {
|
rename_values = {
|
||||||
"extension_builder_stub_name": data.name,
|
"extension_builder_stub_name": data.name,
|
||||||
"extension_builder_stub_camel_name": snake_to_camel(data.id, True),
|
|
||||||
"extension_builder_stub_short_description": data.short_description or "",
|
"extension_builder_stub_short_description": data.short_description or "",
|
||||||
"extension_builder_stub": data.id,
|
"extension_builder_stub": data.id,
|
||||||
"OwnerData": data.owner_data.name,
|
"OwnerData": data.owner_data.name,
|
||||||
@@ -345,7 +328,7 @@ def _rename_extension_builder_stub(data: ExtensionData, extension_dir: Path) ->
|
|||||||
directory=extension_dir_path,
|
directory=extension_dir_path,
|
||||||
old_text=old_text,
|
old_text=old_text,
|
||||||
new_text=new_text,
|
new_text=new_text,
|
||||||
file_extensions=[".py", ".js", ".vue", ".html", ".md", ".json", ".toml"],
|
file_extensions=[".py", ".js", ".html", ".md", ".json", ".toml"],
|
||||||
)
|
)
|
||||||
|
|
||||||
_rename_files_and_dirs_in_directory(
|
_rename_files_and_dirs_in_directory(
|
||||||
@@ -423,7 +406,6 @@ def _parse_extension_data(data: ExtensionData) -> dict:
|
|||||||
"owner_data": {
|
"owner_data": {
|
||||||
"name": data.owner_data.name,
|
"name": data.owner_data.name,
|
||||||
"editable": data.owner_data.editable,
|
"editable": data.owner_data.editable,
|
||||||
"fields": [field.name for field in data.owner_data.fields],
|
|
||||||
"js_fields": [
|
"js_fields": [
|
||||||
field.field_to_js()
|
field.field_to_js()
|
||||||
for field in data.owner_data.fields
|
for field in data.owner_data.fields
|
||||||
@@ -450,18 +432,6 @@ def _parse_extension_data(data: ExtensionData) -> dict:
|
|||||||
],
|
],
|
||||||
"db_fields": [field.field_to_db() for field in data.owner_data.fields],
|
"db_fields": [field.field_to_db() for field in data.owner_data.fields],
|
||||||
"all_fields": [field.field_to_py() for field in data.owner_data.fields],
|
"all_fields": [field.field_to_py() for field in data.owner_data.fields],
|
||||||
"random_fields_values": [
|
|
||||||
field.field_to_random_value() for field in data.owner_data.fields
|
|
||||||
],
|
|
||||||
"public_fields": [
|
|
||||||
field.field_to_py()
|
|
||||||
for field in data.owner_data.fields
|
|
||||||
if field.name
|
|
||||||
in [
|
|
||||||
data.public_page.owner_data_fields.name,
|
|
||||||
data.public_page.owner_data_fields.description,
|
|
||||||
]
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"client_data": {
|
"client_data": {
|
||||||
"name": data.client_data.name,
|
"name": data.client_data.name,
|
||||||
@@ -487,9 +457,6 @@ def _parse_extension_data(data: ExtensionData) -> dict:
|
|||||||
],
|
],
|
||||||
"db_fields": [field.field_to_db() for field in data.client_data.fields],
|
"db_fields": [field.field_to_db() for field in data.client_data.fields],
|
||||||
"all_fields": [field.field_to_py() for field in data.client_data.fields],
|
"all_fields": [field.field_to_py() for field in data.client_data.fields],
|
||||||
"random_fields_values": [
|
|
||||||
field.field_to_random_value() for field in data.client_data.fields
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
"settings_data": {
|
"settings_data": {
|
||||||
"enabled": data.settings_data.enabled,
|
"enabled": data.settings_data.enabled,
|
||||||
@@ -569,7 +536,7 @@ def _rename_files_and_dirs_in_directory(directory, old_text, new_text):
|
|||||||
logger.warning(f"Failed to rename directory {old_dir_path}: {e}")
|
logger.warning(f"Failed to rename directory {old_dir_path}: {e}")
|
||||||
|
|
||||||
|
|
||||||
def _is_excluded_dir(path: str) -> bool:
|
def _is_excluded_dir(path):
|
||||||
for excluded_dir in excluded_dirs:
|
for excluded_dir in excluded_dirs:
|
||||||
if path.startswith(excluded_dir):
|
if path.startswith(excluded_dir):
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -12,12 +12,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -35,40 +29,6 @@ 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,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ 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,
|
||||||
@@ -48,7 +47,6 @@ 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()
|
||||||
@@ -366,7 +364,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 check_payment_status(payment)
|
status = await payment.check_status()
|
||||||
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)
|
||||||
@@ -620,29 +618,7 @@ 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 check_payment_status(payment)
|
return await payment.check_status()
|
||||||
|
|
||||||
|
|
||||||
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(
|
||||||
@@ -776,7 +752,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}")
|
||||||
|
|
||||||
await _send_payment_notification_in_background(wallet.id, payment, conn=conn)
|
send_payment_notification_in_background(wallet, payment)
|
||||||
|
|
||||||
# notify receiver asynchronously
|
# notify receiver asynchronously
|
||||||
from lnbits.tasks import internal_invoice_queue
|
from lnbits.tasks import internal_invoice_queue
|
||||||
@@ -849,8 +825,7 @@ 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
|
||||||
@@ -893,7 +868,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 check_payment_status(payment)
|
status = await payment.check_status()
|
||||||
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"
|
||||||
@@ -1058,13 +1033,3 @@ 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,10 +3,8 @@ 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,
|
||||||
@@ -194,25 +192,3 @@ 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.")
|
|
||||||
|
|||||||
@@ -57,11 +57,11 @@ async def api_get_asset(
|
|||||||
|
|
||||||
|
|
||||||
@asset_router.get(
|
@asset_router.get(
|
||||||
"/{asset_id}/data",
|
"/{asset_id}/binary",
|
||||||
name="Get user asset data",
|
name="Get user asset binary",
|
||||||
summary="Get user asset data data by ID",
|
summary="Get user asset binary data by ID",
|
||||||
)
|
)
|
||||||
async def api_get_asset_data(
|
async def api_get_asset_binary(
|
||||||
asset_id: str,
|
asset_id: str,
|
||||||
user_id: str | None = Depends(optional_user_id),
|
user_id: str | None = Depends(optional_user_id),
|
||||||
) -> Response:
|
) -> Response:
|
||||||
|
|||||||
@@ -4,10 +4,9 @@ import json
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from time import time
|
from time import time
|
||||||
from typing import Annotated
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||||
from fastapi.responses import JSONResponse, RedirectResponse
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
from fastapi_sso.sso.base import OpenID, SSOBase
|
from fastapi_sso.sso.base import OpenID, SSOBase
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
@@ -26,14 +25,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 (
|
from lnbits.core.services.users import update_user_account
|
||||||
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,
|
||||||
check_admin,
|
|
||||||
check_user_exists,
|
check_user_exists,
|
||||||
)
|
)
|
||||||
from lnbits.helpers import (
|
from lnbits.helpers import (
|
||||||
@@ -89,7 +84,6 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
@@ -98,7 +92,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"], active_only=False)
|
account = await get_account_by_pubkey(event["pubkey"])
|
||||||
if not account:
|
if not account:
|
||||||
account = Account(
|
account = Account(
|
||||||
id=uuid4().hex,
|
id=uuid4().hex,
|
||||||
@@ -106,8 +100,6 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
@@ -128,63 +120,6 @@ async def login_usr(data: LoginUsr) -> JSONResponse:
|
|||||||
return _auth_success_response(account.username, account.id, account.email)
|
return _auth_success_response(account.username, account.id, account.email)
|
||||||
|
|
||||||
|
|
||||||
@auth_router.post("/impersonate", description="Login via the User ID of another user")
|
|
||||||
async def impersonate_user(
|
|
||||||
data: LoginUsr,
|
|
||||||
user: User = Depends(check_admin),
|
|
||||||
cookie_access_token: Annotated[str | None, Cookie()] = None,
|
|
||||||
) -> JSONResponse:
|
|
||||||
if not cookie_access_token:
|
|
||||||
raise HTTPException(
|
|
||||||
HTTPStatus.UNAUTHORIZED, "Only cookie based impersonation is allowed."
|
|
||||||
)
|
|
||||||
if data.usr == user.id:
|
|
||||||
raise HTTPException(HTTPStatus.FORBIDDEN, "You cannot impersonate yourself.")
|
|
||||||
if settings.is_admin_user(data.usr):
|
|
||||||
# this check includes the superuser
|
|
||||||
raise HTTPException(
|
|
||||||
HTTPStatus.FORBIDDEN, "You cannot impersonate another admin user."
|
|
||||||
)
|
|
||||||
|
|
||||||
account = await get_account(data.usr)
|
|
||||||
if not account:
|
|
||||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "User ID does not exist.")
|
|
||||||
|
|
||||||
response = _auth_success_response(account.username, account.id, account.email)
|
|
||||||
|
|
||||||
max_age = settings.auth_token_expire_minutes * 60
|
|
||||||
response.set_cookie(
|
|
||||||
"admin_access_token", cookie_access_token, httponly=True, max_age=max_age
|
|
||||||
)
|
|
||||||
response.set_cookie("is_lnbits_user_impersonated", "true", max_age=max_age)
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@auth_router.delete(
|
|
||||||
"/impersonate", description="Stop impersonation and go back to admin"
|
|
||||||
)
|
|
||||||
async def stop_impersonate_user(
|
|
||||||
user: User = Depends(check_user_exists),
|
|
||||||
admin_access_token: Annotated[str | None, Cookie()] = None,
|
|
||||||
) -> JSONResponse:
|
|
||||||
if not admin_access_token:
|
|
||||||
raise HTTPException(
|
|
||||||
HTTPStatus.UNAUTHORIZED,
|
|
||||||
"No admin access token found to stop impersonation.",
|
|
||||||
)
|
|
||||||
response = JSONResponse(
|
|
||||||
{"access_token": admin_access_token, "token_type": "bearer"}
|
|
||||||
)
|
|
||||||
max_age = settings.auth_token_expire_minutes * 60
|
|
||||||
response.set_cookie(
|
|
||||||
"cookie_access_token", admin_access_token, httponly=True, max_age=max_age
|
|
||||||
)
|
|
||||||
response.delete_cookie("admin_access_token")
|
|
||||||
response.delete_cookie("is_access_token_expired")
|
|
||||||
response.delete_cookie("is_lnbits_user_impersonated")
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
@auth_router.get("/acl")
|
@auth_router.get("/acl")
|
||||||
async def api_get_user_acls(
|
async def api_get_user_acls(
|
||||||
request: Request,
|
request: Request,
|
||||||
@@ -363,14 +298,12 @@ 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, active_only=False):
|
if await get_account_by_username(data.username):
|
||||||
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,
|
||||||
@@ -475,7 +408,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.patch("")
|
@auth_router.put("/update")
|
||||||
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:
|
||||||
@@ -491,32 +424,10 @@ 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(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.")
|
||||||
@@ -535,7 +446,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, active_only=False)
|
account = await get_account_by_email(email)
|
||||||
|
|
||||||
if verified_user_id:
|
if verified_user_id:
|
||||||
if account:
|
if account:
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -88,7 +87,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 check_fiat_status(payment)
|
await payment.check_fiat_status()
|
||||||
|
|
||||||
|
|
||||||
async def _handle_stripe_checkout_session_completed(event: dict):
|
async def _handle_stripe_checkout_session_completed(event: dict):
|
||||||
@@ -111,7 +110,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 check_fiat_status(payment)
|
await payment.check_fiat_status()
|
||||||
|
|
||||||
|
|
||||||
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
async def _handle_stripe_subscription_invoice_paid(event: dict):
|
||||||
@@ -156,7 +155,7 @@ async def _handle_stripe_subscription_invoice_paid(event: dict):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await check_fiat_status(payment)
|
await payment.check_fiat_status()
|
||||||
|
|
||||||
|
|
||||||
async def _get_stripe_subscription_payment_options(
|
async def _get_stripe_subscription_payment_options(
|
||||||
@@ -193,7 +192,7 @@ 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 check_fiat_status(payment)
|
await payment.check_fiat_status()
|
||||||
return
|
return
|
||||||
|
|
||||||
if event_type in (
|
if event_type in (
|
||||||
@@ -248,7 +247,7 @@ async def _handle_paypal_subscription_payment(resource: dict):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
await check_fiat_status(payment)
|
await payment.check_fiat_status()
|
||||||
|
|
||||||
|
|
||||||
def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
|
||||||
import httpx
|
|
||||||
from bolt11 import decode as bolt11_decode
|
from bolt11 import decode as bolt11_decode
|
||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from fastapi.requests import Request
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.core.crud.extensions import get_user_extensions
|
from lnbits.core.crud.extensions import get_user_extensions
|
||||||
@@ -16,14 +14,10 @@ from lnbits.core.models import (
|
|||||||
)
|
)
|
||||||
from lnbits.core.models.extensions import (
|
from lnbits.core.models.extensions import (
|
||||||
CreateExtension,
|
CreateExtension,
|
||||||
CreateExtensionReview,
|
|
||||||
Extension,
|
Extension,
|
||||||
ExtensionConfig,
|
ExtensionConfig,
|
||||||
ExtensionMeta,
|
ExtensionMeta,
|
||||||
ExtensionRelease,
|
ExtensionRelease,
|
||||||
ExtensionReview,
|
|
||||||
ExtensionReviewPaymentRequest,
|
|
||||||
ExtensionReviewsStatus,
|
|
||||||
InstallableExtension,
|
InstallableExtension,
|
||||||
PayToEnableInfo,
|
PayToEnableInfo,
|
||||||
ReleasePaymentInfo,
|
ReleasePaymentInfo,
|
||||||
@@ -40,7 +34,6 @@ from lnbits.core.services.extensions import (
|
|||||||
install_extension,
|
install_extension,
|
||||||
uninstall_extension,
|
uninstall_extension,
|
||||||
)
|
)
|
||||||
from lnbits.db import Page
|
|
||||||
from lnbits.decorators import (
|
from lnbits.decorators import (
|
||||||
check_account_exists,
|
check_account_exists,
|
||||||
check_account_id_exists,
|
check_account_id_exists,
|
||||||
@@ -596,49 +589,3 @@ async def extensions(account_id: AccountId = Depends(check_account_id_exists)):
|
|||||||
for ext in installable_exts
|
for ext in installable_exts
|
||||||
]
|
]
|
||||||
return extension_data
|
return extension_data
|
||||||
|
|
||||||
|
|
||||||
@extension_router.get(
|
|
||||||
"/reviews/tags",
|
|
||||||
dependencies=[Depends(check_account_exists)],
|
|
||||||
)
|
|
||||||
async def get_extension_reviews_tags() -> list[ExtensionReviewsStatus]:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
resp = await client.get(settings.lnbits_extensions_reviews_url + "/tags")
|
|
||||||
resp.raise_for_status()
|
|
||||||
data = resp.json()
|
|
||||||
return [ExtensionReviewsStatus(**item) for item in data]
|
|
||||||
|
|
||||||
|
|
||||||
@extension_router.get(
|
|
||||||
"/reviews/{ext_id}",
|
|
||||||
dependencies=[Depends(check_account_exists)],
|
|
||||||
)
|
|
||||||
async def get_extension_reviews(ext_id: str, request: Request) -> Page[ExtensionReview]:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
query_string = str(request.query_params)
|
|
||||||
resp = await client.get(
|
|
||||||
settings.lnbits_extensions_reviews_url + f"/reviews/{ext_id}?{query_string}"
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
reviews = resp.json()
|
|
||||||
return Page(
|
|
||||||
data=[ExtensionReview(**item) for item in reviews["data"]],
|
|
||||||
total=reviews["total"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@extension_router.put(
|
|
||||||
"/reviews",
|
|
||||||
dependencies=[Depends(check_account_exists)],
|
|
||||||
)
|
|
||||||
async def create_extension_review(
|
|
||||||
data: CreateExtensionReview,
|
|
||||||
) -> ExtensionReviewPaymentRequest:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
resp = await client.post(
|
|
||||||
settings.lnbits_extensions_reviews_url + "/reviews", json=data.dict()
|
|
||||||
)
|
|
||||||
resp.raise_for_status()
|
|
||||||
payment_request = resp.json()
|
|
||||||
return ExtensionReviewPaymentRequest(**payment_request)
|
|
||||||
|
|||||||
@@ -42,38 +42,54 @@ async def robots():
|
|||||||
|
|
||||||
|
|
||||||
@generic_router.get(
|
@generic_router.get(
|
||||||
"/extensions/builder/preview/{ext_id}/{resource}",
|
"/extensions/builder/preview/{ext_id}",
|
||||||
name="extensions builder",
|
name="extensions builder",
|
||||||
dependencies=[Depends(check_extension_builder)],
|
dependencies=[Depends(check_extension_builder)],
|
||||||
)
|
)
|
||||||
async def extensions_builder_preview(
|
async def extensions_builder_preview(
|
||||||
|
request: Request,
|
||||||
ext_id: str,
|
ext_id: str,
|
||||||
resource: str | None = None,
|
|
||||||
page_name: str | None = None,
|
page_name: str | None = None,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
) -> FileResponse:
|
) -> HTMLResponse:
|
||||||
working_dir_name = "preview_" + sha256(user.id.encode("utf-8")).hexdigest()
|
working_dir_name = "preview_" + sha256(user.id.encode("utf-8")).hexdigest()
|
||||||
|
html_file_name = "index.html"
|
||||||
file_name = "index"
|
|
||||||
if page_name == "public_page":
|
if page_name == "public_page":
|
||||||
file_name = "public_page"
|
html_file_name = "public_page.html"
|
||||||
|
|
||||||
resource_path = Path(
|
html_file_path = Path(
|
||||||
settings.extension_builder_working_dir_path,
|
|
||||||
"extension_builder_stub",
|
"extension_builder_stub",
|
||||||
ext_id,
|
ext_id,
|
||||||
working_dir_name,
|
working_dir_name,
|
||||||
ext_id,
|
ext_id,
|
||||||
"static",
|
"templates",
|
||||||
|
ext_id,
|
||||||
|
html_file_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
file_ext = ".vue" if resource == "template" else ".js"
|
html_file_full_path = Path(
|
||||||
file_full_path = Path(resource_path, file_name + file_ext)
|
settings.extension_builder_working_dir_path, html_file_path
|
||||||
|
)
|
||||||
|
|
||||||
if not file_full_path.is_file():
|
if not html_file_full_path.is_file():
|
||||||
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|
return template_renderer().TemplateResponse(
|
||||||
|
request,
|
||||||
|
"error.html",
|
||||||
|
{
|
||||||
|
"status_code": 404,
|
||||||
|
"message": f"Extension {ext_id} not found, refresh Preview.",
|
||||||
|
},
|
||||||
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
)
|
||||||
|
|
||||||
|
response = template_renderer().TemplateResponse(
|
||||||
|
request,
|
||||||
|
html_file_path.as_posix(),
|
||||||
|
{
|
||||||
|
"user": user.json(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
response = FileResponse(file_full_path.absolute().as_posix())
|
|
||||||
response.headers["Content-Security-Policy"] = (
|
response.headers["Content-Security-Policy"] = (
|
||||||
"default-src 'self'; "
|
"default-src 'self'; "
|
||||||
"style-src 'self' 'unsafe-inline'; "
|
"style-src 'self' 'unsafe-inline'; "
|
||||||
@@ -192,9 +208,6 @@ admin_ui_checks = [Depends(check_admin), Depends(check_admin_ui)]
|
|||||||
@generic_router.get(
|
@generic_router.get(
|
||||||
"/extensions/builder", dependencies=[Depends(check_extension_builder)]
|
"/extensions/builder", dependencies=[Depends(check_extension_builder)]
|
||||||
)
|
)
|
||||||
@generic_router.get(
|
|
||||||
"/extensions/builder/preview", dependencies=[Depends(check_extension_builder)]
|
|
||||||
)
|
|
||||||
async def index(
|
async def index(
|
||||||
request: Request, user: User = Depends(check_user_exists)
|
request: Request, user: User = Depends(check_user_exists)
|
||||||
) -> HTMLResponse:
|
) -> HTMLResponse:
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ 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.models import (
|
from lnbits.core.models import (
|
||||||
AccountFilters,
|
AccountFilters,
|
||||||
AccountOverview,
|
AccountOverview,
|
||||||
@@ -74,7 +72,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, active_only=False)
|
user = await get_user(user_id)
|
||||||
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
|
||||||
@@ -159,8 +157,11 @@ async def api_users_delete_user(
|
|||||||
user_id: str, account: Account = Depends(check_admin)
|
user_id: str, account: Account = Depends(check_admin)
|
||||||
) -> SimpleStatus:
|
) -> SimpleStatus:
|
||||||
wallets = await get_wallets(user_id, deleted=False)
|
wallets = await get_wallets(user_id, deleted=False)
|
||||||
for wallet in wallets:
|
if len(wallets) > 0:
|
||||||
await delete_wallet_by_id(wallet.id)
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Cannot delete user with wallets.",
|
||||||
|
)
|
||||||
|
|
||||||
if user_id == settings.super_user:
|
if user_id == settings.super_user:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -198,7 +199,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.put(
|
@users_router.get(
|
||||||
"/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",
|
||||||
@@ -221,46 +222,9 @@ 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)
|
||||||
|
|
||||||
|
|
||||||
@users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user")
|
@users_router.post("/user/{user_id}/wallet", name="Create a new wallet for user")
|
||||||
@@ -283,7 +247,7 @@ async def api_users_create_user_wallet(
|
|||||||
"/user/{user_id}/wallet/{wallet}/undelete", name="Reactivate deleted wallet"
|
"/user/{user_id}/wallet/{wallet}/undelete", name="Reactivate deleted wallet"
|
||||||
)
|
)
|
||||||
async def api_users_undelete_user_wallet(user_id: str, wallet: str) -> SimpleStatus:
|
async def api_users_undelete_user_wallet(user_id: str, wallet: str) -> SimpleStatus:
|
||||||
wal = await get_wallet(wallet, deleted=True)
|
wal = await get_wallet(wallet)
|
||||||
if not wal:
|
if not wal:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=HTTPStatus.NOT_FOUND,
|
status_code=HTTPStatus.NOT_FOUND,
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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,
|
||||||
)
|
)
|
||||||
@@ -38,6 +37,7 @@ 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,7 +134,9 @@ 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")
|
||||||
|
|
||||||
clear_wallet_cache(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}")
|
||||||
|
|
||||||
wallet.adminkey = uuid4().hex
|
wallet.adminkey = uuid4().hex
|
||||||
wallet.inkey = uuid4().hex
|
wallet.inkey = uuid4().hex
|
||||||
|
|||||||
+37
-9
@@ -12,6 +12,7 @@ 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
|
||||||
|
|
||||||
@@ -55,6 +56,14 @@ 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>"
|
||||||
@@ -317,7 +326,30 @@ 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"
|
||||||
|
return datetime.strptime(value, f)
|
||||||
|
|
||||||
|
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
|
||||||
@@ -630,7 +662,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(f":{field}")
|
placeholders.append(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 = ", ".join([f'"{key}"' for key in keys])
|
fields = ", ".join([f'"{key}"' for key in keys])
|
||||||
values = ", ".join(placeholders)
|
values = ", ".join(placeholders)
|
||||||
@@ -648,8 +680,9 @@ 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}" = :{field}')
|
fields.append(f'"{field}" = {placeholder}')
|
||||||
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
|
||||||
|
|
||||||
@@ -667,12 +700,7 @@ 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):
|
||||||
if DB_TYPE == SQLITE:
|
_dict[key] = value.timestamp()
|
||||||
_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)
|
||||||
@@ -728,7 +756,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:
|
||||||
_dict[key] = value.replace(tzinfo=timezone.utc)
|
_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,7 +261,6 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ def register_exception_handlers(app: FastAPI): # noqa: C901
|
|||||||
logger.error(f"RequestValidationError: {exc!s}")
|
logger.error(f"RequestValidationError: {exc!s}")
|
||||||
return render_html_error(request, exc) or JSONResponse(
|
return render_html_error(request, exc) or JSONResponse(
|
||||||
status_code=HTTPStatus.BAD_REQUEST,
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
content={"detail": [dict(e) for e in exc.errors()]},
|
content={"detail": str(exc)},
|
||||||
)
|
)
|
||||||
|
|
||||||
@app.exception_handler(HTTPException)
|
@app.exception_handler(HTTPException)
|
||||||
|
|||||||
+50
-16
@@ -70,11 +70,56 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
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
|
||||||
|
|
||||||
# used in base.html
|
window_settings = {
|
||||||
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
"AD_SPACE": settings.lnbits_ad_space,
|
||||||
t.env.globals["LNBITS_APPLE_TOUCH_ICON"] = settings.lnbits_apple_touch_icon
|
"AD_SPACE_ENABLED": settings.lnbits_ad_space_enabled,
|
||||||
t.env.globals["SETTINGS"] = settings.to_public().dict(by_alias=True)
|
"AD_SPACE_TITLE": settings.lnbits_ad_space_title,
|
||||||
t.env.globals["CURRENCIES"] = list(currencies.keys())
|
"EXTENSIONS": list(settings.lnbits_installed_extensions_ids),
|
||||||
|
"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_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"]
|
||||||
@@ -88,9 +133,6 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@@ -348,14 +390,6 @@ def camel_to_snake(name: str) -> str:
|
|||||||
return name.lower()
|
return name.lower()
|
||||||
|
|
||||||
|
|
||||||
def snake_to_camel(name: str, capitalize_first: bool | None = False) -> str:
|
|
||||||
components = name.split("_")
|
|
||||||
camel = components[0] + "".join(x.capitalize() for x in components[1:])
|
|
||||||
if capitalize_first:
|
|
||||||
camel = camel[0].upper() + camel[1:]
|
|
||||||
return camel
|
|
||||||
|
|
||||||
|
|
||||||
def is_camel_case(v: str) -> bool:
|
def is_camel_case(v: str) -> bool:
|
||||||
return re.match(r"^[A-Z][a-z0-9]+([A-Z][a-z0-9]+)*$", v) is not None
|
return re.match(r"^[A-Z][a-z0-9]+([A-Z][a-z0-9]+)*$", v) is not None
|
||||||
|
|
||||||
|
|||||||
@@ -41,14 +41,6 @@ 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:
|
||||||
@@ -60,13 +52,6 @@ class ExtensionsSettings(LNbitsSettings):
|
|||||||
lnbits_user_default_extensions: list[str] = Field(default=[])
|
lnbits_user_default_extensions: list[str] = Field(default=[])
|
||||||
lnbits_extensions_deactivate_all: bool = Field(default=False)
|
lnbits_extensions_deactivate_all: bool = Field(default=False)
|
||||||
lnbits_extensions_builder_activate_non_admins: bool = Field(default=False)
|
lnbits_extensions_builder_activate_non_admins: bool = Field(default=False)
|
||||||
lnbits_extensions_reviews_url: str = Field(
|
|
||||||
default="https://demo.lnbits.com/paidreviews/api/v1/AdFzLjzuKFLsdk4Bcnff6r",
|
|
||||||
description="""
|
|
||||||
URL for the paid reviews.
|
|
||||||
Regular users can view this URL (not secret).
|
|
||||||
""",
|
|
||||||
)
|
|
||||||
lnbits_extensions_manifests: list[str] = Field(
|
lnbits_extensions_manifests: list[str] = Field(
|
||||||
default=[
|
default=[
|
||||||
"https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json"
|
"https://raw.githubusercontent.com/lnbits/lnbits-extensions/main/extensions.json"
|
||||||
@@ -260,11 +245,6 @@ 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(
|
||||||
@@ -319,10 +299,6 @@ class AssetSettings(LNbitsSettings):
|
|||||||
"heic",
|
"heic",
|
||||||
"heif",
|
"heif",
|
||||||
"heics",
|
"heics",
|
||||||
"text/plain",
|
|
||||||
"text/json" "text/xml",
|
|
||||||
"application/json",
|
|
||||||
"application/pdf",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
lnbits_asset_thumbnail_width: int = Field(default=128, ge=0)
|
lnbits_asset_thumbnail_width: int = Field(default=128, ge=0)
|
||||||
@@ -991,7 +967,6 @@ 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)
|
||||||
@@ -1140,123 +1115,6 @@ 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")
|
|
||||||
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"
|
|
||||||
)
|
|
||||||
|
|
||||||
@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,
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
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
+10
-10
File diff suppressed because one or more lines are too long
+29
-3
@@ -1,5 +1,30 @@
|
|||||||
window.app.component('lnbits-error', {
|
<template>
|
||||||
template: '#lnbits-error',
|
<div class="text-center q-pa-md flex flex-center">
|
||||||
|
<div>
|
||||||
|
<div class="error-code" v-text="code"></div>
|
||||||
|
<div class="error-message" v-text="message"></div>
|
||||||
|
<div class="q-mx-auto q-mt-lg justify-center" style="width: max-content">
|
||||||
|
<q-btn
|
||||||
|
v-if="isExtension"
|
||||||
|
color="primary"
|
||||||
|
@click="goToExtension()"
|
||||||
|
label="Go To Extension"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-else-if="g.isUserAuthorized"
|
||||||
|
color="primary"
|
||||||
|
@click="goToWallet()"
|
||||||
|
label="Go to Wallet"
|
||||||
|
></q-btn>
|
||||||
|
<q-btn v-else color="primary" @click="goBack()" label="Go Back"></q-btn>
|
||||||
|
<span class="q-mx-md">OR</span>
|
||||||
|
<q-btn color="secondary" @click="goHome()" label="Go Home"></q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
props: ['dynamic', 'code', 'message'],
|
props: ['dynamic', 'code', 'message'],
|
||||||
computed: {
|
computed: {
|
||||||
isExtension() {
|
isExtension() {
|
||||||
@@ -49,4 +74,5 @@ window.app.component('lnbits-error', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import LnbitsError from './lnbits-error.vue'
|
||||||
|
|
||||||
|
window.LnbitsError = LnbitsError
|
||||||
@@ -171,7 +171,6 @@ 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',
|
||||||
@@ -179,8 +178,6 @@ 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',
|
||||||
@@ -189,28 +186,7 @@ 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_label: 'Reviews server URL',
|
|
||||||
reviews_url_hint:
|
|
||||||
'Full PaidReviews URL including the settings id (e.g. https://example.com/paidreviews/SETTINGS_ID)',
|
|
||||||
reviews_open: 'View reviews',
|
|
||||||
reviews_leave: 'Leave a review',
|
|
||||||
reviews_name: 'Your name',
|
|
||||||
reviews_comment: 'Your review',
|
|
||||||
reviews_rating: 'Rating',
|
|
||||||
reviews_submit: 'Submit review',
|
|
||||||
reviews_loading: 'Loading reviews...',
|
|
||||||
reviews_refresh: 'Refresh reviews',
|
|
||||||
reviews_error_load: 'Could not load reviews',
|
|
||||||
reviews_url_not_configured: 'Reviews URL not configured',
|
|
||||||
reviews_pay_invoice: 'Pay invoice',
|
|
||||||
reviews_invoice_paid: 'Invoice paid',
|
|
||||||
reviews_invoice_title: 'Pay this invoice to submit your review',
|
|
||||||
reviews_count: 'Reviews',
|
|
||||||
no_reviews: 'No reviews yet',
|
|
||||||
extension_has_free_release: 'Has free releases',
|
extension_has_free_release: 'Has free releases',
|
||||||
extension_has_paid_release: 'Has paid releases',
|
extension_has_paid_release: 'Has paid releases',
|
||||||
extension_depends_on: 'Depends on:',
|
extension_depends_on: 'Depends on:',
|
||||||
@@ -285,7 +261,7 @@ 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',
|
||||||
@@ -523,7 +499,6 @@ 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 (optional)',
|
|
||||||
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',
|
||||||
@@ -664,15 +639,6 @@ 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_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 (check quasar icons)',
|
|
||||||
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',
|
||||||
@@ -709,28 +675,7 @@ 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',
|
|
||||||
stop_user_impersonation: 'Stop User Impersonation',
|
|
||||||
components: 'Components',
|
components: 'Components',
|
||||||
long_running_endpoints: 'Top 5 Long Running Endpoints',
|
long_running_endpoints: 'Top 5 Long Running Endpoints',
|
||||||
http_request_methods: 'HTTP Request Methods',
|
http_request_methods: 'HTTP Request Methods',
|
||||||
|
|||||||
+3
-20
@@ -66,7 +66,7 @@ window._lnbitsApi = {
|
|||||||
name: name
|
name: name
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
register(username, email, password, password_repeat, invitation_code) {
|
register(username, email, password, password_repeat) {
|
||||||
return axios({
|
return axios({
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/api/v1/auth/register',
|
url: '/api/v1/auth/register',
|
||||||
@@ -74,8 +74,7 @@ window._lnbitsApi = {
|
|||||||
username,
|
username,
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
password_repeat,
|
password_repeat
|
||||||
invitation_code
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -124,19 +123,6 @@ window._lnbitsApi = {
|
|||||||
url: '/api/v1/auth/logout'
|
url: '/api/v1/auth/logout'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
impersonateUser(usr) {
|
|
||||||
return axios({
|
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/impersonate',
|
|
||||||
data: {usr}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
stopImpersonation() {
|
|
||||||
return axios({
|
|
||||||
method: 'DELETE',
|
|
||||||
url: '/api/v1/auth/impersonate'
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getAuthenticatedUser() {
|
getAuthenticatedUser() {
|
||||||
return this.request('get', '/api/v1/auth')
|
return this.request('get', '/api/v1/auth')
|
||||||
},
|
},
|
||||||
@@ -148,16 +134,13 @@ 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 => {
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ window.LNbits = {
|
|||||||
wallets: data.wallets,
|
wallets: data.wallets,
|
||||||
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,
|
|
||||||
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) => {
|
||||||
|
|||||||
@@ -3,38 +3,44 @@ window.app.component(QrcodeVue)
|
|||||||
window.app.component('lnbits-extension-rating', {
|
window.app.component('lnbits-extension-rating', {
|
||||||
template: '#lnbits-extension-rating',
|
template: '#lnbits-extension-rating',
|
||||||
name: 'lnbits-extension-rating',
|
name: 'lnbits-extension-rating',
|
||||||
|
props: ['rating']
|
||||||
|
})
|
||||||
|
|
||||||
|
window.app.component('lnbits-fsat', {
|
||||||
|
template: '<span>{{ fsat }}</span>',
|
||||||
props: {
|
props: {
|
||||||
rating: {
|
amount: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
|
||||||
count: {
|
|
||||||
type: Number,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
clickable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
displayRating() {
|
fsat() {
|
||||||
return Math.round((this.rating || 0) * 2) / 2
|
return LNbits.utils.formatSat(this.amount)
|
||||||
},
|
|
||||||
hasData() {
|
|
||||||
return this.count !== null && this.count !== undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleClick() {
|
|
||||||
if (!this.clickable) return
|
|
||||||
this.$emit('click')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-manage', {
|
window.app.component('lnbits-manage', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
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
|
||||||
@@ -48,8 +54,10 @@ window.app.component('lnbits-manage', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-payment-details', {
|
window.app.component('lnbits-payment-details', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-payment-details',
|
template: '#lnbits-payment-details',
|
||||||
props: ['payment'],
|
props: ['payment'],
|
||||||
|
mixins: [window.windowMixin],
|
||||||
computed: {
|
computed: {
|
||||||
hasPreimage() {
|
hasPreimage() {
|
||||||
return (
|
return (
|
||||||
@@ -93,6 +101,7 @@ window.app.component('lnbits-payment-details', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-lnurlpay-success-action', {
|
window.app.component('lnbits-lnurlpay-success-action', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-lnurlpay-success-action',
|
template: '#lnbits-lnurlpay-success-action',
|
||||||
props: ['payment', 'success_action'],
|
props: ['payment', 'success_action'],
|
||||||
data() {
|
data() {
|
||||||
@@ -112,6 +121,7 @@ window.app.component('lnbits-lnurlpay-success-action', {
|
|||||||
|
|
||||||
window.app.component('lnbits-notifications-btn', {
|
window.app.component('lnbits-notifications-btn', {
|
||||||
template: '#lnbits-notifications-btn',
|
template: '#lnbits-notifications-btn',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['pubkey'],
|
props: ['pubkey'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -282,6 +292,7 @@ window.app.component('lnbits-notifications-btn', {
|
|||||||
|
|
||||||
window.app.component('lnbits-dynamic-fields', {
|
window.app.component('lnbits-dynamic-fields', {
|
||||||
template: '#lnbits-dynamic-fields',
|
template: '#lnbits-dynamic-fields',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['options', 'modelValue'],
|
props: ['options', 'modelValue'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -314,6 +325,7 @@ window.app.component('lnbits-dynamic-fields', {
|
|||||||
|
|
||||||
window.app.component('lnbits-dynamic-chips', {
|
window.app.component('lnbits-dynamic-chips', {
|
||||||
template: '#lnbits-dynamic-chips',
|
template: '#lnbits-dynamic-chips',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['modelValue'],
|
props: ['modelValue'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -344,6 +356,7 @@ window.app.component('lnbits-dynamic-chips', {
|
|||||||
|
|
||||||
window.app.component('lnbits-update-balance', {
|
window.app.component('lnbits-update-balance', {
|
||||||
template: '#lnbits-update-balance',
|
template: '#lnbits-update-balance',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['wallet_id', 'small_btn'],
|
props: ['wallet_id', 'small_btn'],
|
||||||
computed: {
|
computed: {
|
||||||
admin() {
|
admin() {
|
||||||
@@ -382,6 +395,7 @@ window.app.component('lnbits-update-balance', {
|
|||||||
|
|
||||||
window.app.component('user-id-only', {
|
window.app.component('user-id-only', {
|
||||||
template: '#user-id-only',
|
template: '#user-id-only',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: {
|
props: {
|
||||||
allowed_new_users: Boolean,
|
allowed_new_users: Boolean,
|
||||||
authAction: String,
|
authAction: String,
|
||||||
@@ -425,6 +439,7 @@ window.app.component('user-id-only', {
|
|||||||
|
|
||||||
window.app.component('username-password', {
|
window.app.component('username-password', {
|
||||||
template: '#username-password',
|
template: '#username-password',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: {
|
props: {
|
||||||
allowed_new_users: Boolean,
|
allowed_new_users: Boolean,
|
||||||
authMethods: Array,
|
authMethods: Array,
|
||||||
@@ -432,10 +447,8 @@ 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: [
|
||||||
@@ -448,10 +461,8 @@ 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,
|
||||||
confirmationMethod: 'code',
|
keycloakOrg: LNBITS_AUTH_KEYCLOAK_ORG || 'Keycloak',
|
||||||
confirmationEmail: '',
|
keycloakIcon: LNBITS_AUTH_KEYCLOAK_ICON
|
||||||
confirmationCode: this.invitationCode || '',
|
|
||||||
showConfirmationCode: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -464,7 +475,6 @@ 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() {
|
||||||
@@ -556,25 +566,6 @@ 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() {}
|
||||||
@@ -686,6 +677,7 @@ window.app.component('lnbits-stat', {
|
|||||||
|
|
||||||
window.app.component('lnbits-node-qrcode', {
|
window.app.component('lnbits-node-qrcode', {
|
||||||
props: ['info'],
|
props: ['info'],
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: `
|
template: `
|
||||||
<q-card class="my-card">
|
<q-card class="my-card">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
@@ -764,6 +756,7 @@ window.app.component('lnbits-node-info', {
|
|||||||
showDialog: false
|
showDialog: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mixins: [window.windowMixin],
|
||||||
methods: {
|
methods: {
|
||||||
shortenNodeId(nodeId) {
|
shortenNodeId(nodeId) {
|
||||||
return nodeId
|
return nodeId
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-assets-config', {
|
window.app.component('lnbits-admin-assets-config', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-assets-config',
|
template: '#lnbits-admin-assets-config',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
newAllowedAssetMimeType: '',
|
newAllowedAssetMimeType: '',
|
||||||
@@ -16,7 +17,6 @@ window.app.component('lnbits-admin-assets-config', {
|
|||||||
this.newAllowedAssetMimeType
|
this.newAllowedAssetMimeType
|
||||||
)
|
)
|
||||||
this.newAllowedAssetMimeType = ''
|
this.newAllowedAssetMimeType = ''
|
||||||
this.formData.touch = null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeAllowedAssetMimeType(type) {
|
removeAllowedAssetMimeType(type) {
|
||||||
@@ -24,21 +24,18 @@ window.app.component('lnbits-admin-assets-config', {
|
|||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.formData.lnbits_assets_allowed_mime_types.splice(index, 1)
|
this.formData.lnbits_assets_allowed_mime_types.splice(index, 1)
|
||||||
}
|
}
|
||||||
this.formData.touch = null
|
|
||||||
},
|
},
|
||||||
addNewNoLimitUser() {
|
addNewNoLimitUser() {
|
||||||
if (this.newNoLimitUser) {
|
if (this.newNoLimitUser) {
|
||||||
this.removeNoLimitUser(this.newNoLimitUser)
|
this.removeNoLimitUser(this.newNoLimitUser)
|
||||||
this.formData.lnbits_assets_no_limit_users.push(this.newNoLimitUser)
|
this.formData.lnbits_assets_no_limit_users.push(this.newNoLimitUser)
|
||||||
this.newNoLimitUser = ''
|
this.newNoLimitUser = ''
|
||||||
this.formData.touch = null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeNoLimitUser(user) {
|
removeNoLimitUser(user) {
|
||||||
if (user) {
|
if (user) {
|
||||||
this.formData.lnbits_assets_no_limit_users =
|
this.formData.lnbits_assets_no_limit_users =
|
||||||
this.formData.lnbits_assets_no_limit_users.filter(u => u !== user)
|
this.formData.lnbits_assets_no_limit_users.filter(u => u !== user)
|
||||||
this.formData.touch = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-audit', {
|
window.app.component('lnbits-admin-audit', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-audit',
|
template: '#lnbits-admin-audit',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formAddIncludePath: '',
|
formAddIncludePath: '',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-exchange-providers', {
|
window.app.component('lnbits-admin-exchange-providers', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-exchange-providers',
|
template: '#lnbits-admin-exchange-providers',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
exchangeData: {
|
exchangeData: {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-extensions', {
|
window.app.component('lnbits-admin-extensions', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-extensions',
|
template: '#lnbits-admin-extensions',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formAddExtensionsManifest: ''
|
formAddExtensionsManifest: ''
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-fiat-providers', {
|
window.app.component('lnbits-admin-fiat-providers', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-fiat-providers',
|
template: '#lnbits-admin-fiat-providers',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formAddStripeUser: '',
|
formAddStripeUser: '',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-admin-funding-sources', {
|
window.app.component('lnbits-admin-funding-sources', {
|
||||||
template: '#lnbits-admin-funding-sources',
|
template: '#lnbits-admin-funding-sources',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['form-data', 'allowed-funding-sources'],
|
props: ['form-data', 'allowed-funding-sources'],
|
||||||
methods: {
|
methods: {
|
||||||
getFundingSourceLabel(item) {
|
getFundingSourceLabel(item) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-funding', {
|
window.app.component('lnbits-admin-funding', {
|
||||||
props: ['is-super-user', 'form-data', 'settings'],
|
props: ['is-super-user', 'form-data', 'settings'],
|
||||||
template: '#lnbits-admin-funding',
|
template: '#lnbits-admin-funding',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
auditData: []
|
auditData: []
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-notifications', {
|
window.app.component('lnbits-admin-notifications', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-notifications',
|
template: '#lnbits-admin-notifications',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
nostrNotificationIdentifier: '',
|
nostrNotificationIdentifier: '',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-security', {
|
window.app.component('lnbits-admin-security', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-security',
|
template: '#lnbits-admin-security',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
logs: [],
|
logs: [],
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
window.app.component('lnbits-admin-server', {
|
window.app.component('lnbits-admin-server', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-server'
|
template: '#lnbits-admin-server',
|
||||||
|
mixins: [window.windowMixin]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-admin-site-customisation', {
|
window.app.component('lnbits-admin-site-customisation', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-site-customisation',
|
template: '#lnbits-admin-site-customisation',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
lnbits_theme_options: [
|
lnbits_theme_options: [
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
window.app.component('lnbits-admin-users', {
|
window.app.component('lnbits-admin-users', {
|
||||||
props: ['form-data'],
|
props: ['form-data'],
|
||||||
template: '#lnbits-admin-users',
|
template: '#lnbits-admin-users',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
formAddUser: '',
|
formAddUser: '',
|
||||||
formAddAdmin: '',
|
formAddAdmin: ''
|
||||||
formAddActivationCode: '',
|
|
||||||
showReusableActivationCode: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -33,24 +32,6 @@ 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,5 +1,6 @@
|
|||||||
window.app.component('lnbits-disclaimer', {
|
window.app.component('lnbits-disclaimer', {
|
||||||
template: '#lnbits-disclaimer',
|
template: '#lnbits-disclaimer',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
computed: {
|
computed: {
|
||||||
showDisclaimer() {
|
showDisclaimer() {
|
||||||
return !g.disclaimerShown && g.isUserAuthorized
|
return !g.disclaimerShown && g.isUserAuthorized
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
window.app.component('lnbits-drawer', {
|
window.app.component('lnbits-drawer', {
|
||||||
template: '#lnbits-drawer'
|
template: '#lnbits-drawer',
|
||||||
|
mixins: [window.windowMixin]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
window.app.component('lnbits-footer', {
|
window.app.component('lnbits-footer', {
|
||||||
template: '#lnbits-footer',
|
template: '#lnbits-footer',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
computed: {
|
computed: {
|
||||||
title() {
|
|
||||||
return `${this.g.settings.siteTitle}, ${this.g.settings.siteTagline}`
|
|
||||||
},
|
|
||||||
version() {
|
version() {
|
||||||
return this.$t('lnbits_version') + ': ' + this.g.settings.version
|
return this.LNBITS_VERSION || 'unknown version'
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return `${this.SITE_TITLE}, ${this.SITE_TAGLINE}`
|
||||||
|
},
|
||||||
|
showFooter() {
|
||||||
|
return this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
window.app.component('lnbits-header-wallets', {
|
window.app.component('lnbits-header-wallets', {
|
||||||
template: '#lnbits-header-wallets'
|
template: '#lnbits-header-wallets',
|
||||||
|
mixins: [window.windowMixin]
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,9 +1,49 @@
|
|||||||
window.app.component('lnbits-header', {
|
window.app.component('lnbits-header', {
|
||||||
template: '#lnbits-header',
|
template: '#lnbits-header',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
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 ||
|
||||||
@@ -21,28 +61,5 @@ window.app.component('lnbits-header', {
|
|||||||
return 'User'
|
return 'User'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async stopImpersonation() {
|
|
||||||
try {
|
|
||||||
await LNbits.api.stopImpersonation()
|
|
||||||
LNbits.utils.restoreLocalStorage('impersonation')
|
|
||||||
window.location = '/users'
|
|
||||||
} catch (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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-home-logos', {
|
window.app.component('lnbits-home-logos', {
|
||||||
template: '#lnbits-home-logos',
|
template: '#lnbits-home-logos',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
logos: [
|
logos: [
|
||||||
@@ -95,8 +96,8 @@ window.app.component('lnbits-home-logos', {
|
|||||||
showLogos() {
|
showLogos() {
|
||||||
return (
|
return (
|
||||||
this.g.isSatsDenomination &&
|
this.g.isSatsDenomination &&
|
||||||
this.g.settings.siteTitle == 'LNbits' &&
|
this.SITE_TITLE == 'LNbits' &&
|
||||||
this.g.settings.showHomePageElements == true
|
this.LNBITS_SHOW_HOME_PAGE_ELEMENTS == true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-label-selector', {
|
window.app.component('lnbits-label-selector', {
|
||||||
template: '#lnbits-label-selector',
|
template: '#lnbits-label-selector',
|
||||||
props: ['labels'],
|
props: ['labels'],
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
labelFilter: '',
|
labelFilter: '',
|
||||||
|
|||||||
@@ -1,16 +1,6 @@
|
|||||||
window.app.component('lnbits-language-dropdown', {
|
window.app.component('lnbits-language-dropdown', {
|
||||||
template: '#lnbits-language-dropdown',
|
template: '#lnbits-language-dropdown',
|
||||||
computed: {
|
mixins: [window.windowMixin],
|
||||||
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
|
||||||
@@ -19,7 +9,6 @@ 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() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
window.app.component('lnbits-manage-extension-list', {
|
window.app.component('lnbits-manage-extension-list', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-manage-extension-list',
|
template: '#lnbits-manage-extension-list',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-manage-wallet-list', {
|
window.app.component('lnbits-manage-wallet-list', {
|
||||||
template: '#lnbits-manage-wallet-list',
|
template: '#lnbits-manage-wallet-list',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
activeWalletId: null
|
activeWalletId: null
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
window.app.component('lnbits-payment-list', {
|
window.app.component('lnbits-payment-list', {
|
||||||
template: '#lnbits-payment-list',
|
template: '#lnbits-payment-list',
|
||||||
props: ['wallet', 'paymentFilter'],
|
props: ['wallet', 'paymentFilter'],
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
payments: [],
|
payments: [],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-qrcode-lnurl', {
|
window.app.component('lnbits-qrcode-lnurl', {
|
||||||
template: '#lnbits-qrcode-lnurl',
|
template: '#lnbits-qrcode-lnurl',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: {
|
props: {
|
||||||
url: {
|
url: {
|
||||||
required: true,
|
required: true,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
window.app.component('lnbits-qrcode', {
|
window.app.component('lnbits-qrcode', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-qrcode',
|
template: '#lnbits-qrcode',
|
||||||
components: {
|
components: {
|
||||||
QrcodeVue: QrcodeVue.default
|
QrcodeVue: QrcodeVue.default
|
||||||
@@ -34,7 +35,7 @@ window.app.component('lnbits-qrcode', {
|
|||||||
},
|
},
|
||||||
logo: {
|
logo: {
|
||||||
type: String,
|
type: String,
|
||||||
default: window.g.settings.qrLogo || null
|
default: LNBITS_QR_LOGO
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
window.app.component('lnbits-theme', {
|
window.app.component('lnbits-theme', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
watch: {
|
watch: {
|
||||||
'g.walletFlip'(val) {
|
'g.walletFlip'(val) {
|
||||||
this.$q.localStorage.setItem('lnbits.walletFlip', val)
|
this.$q.localStorage.setItem('lnbits.walletFlip', val)
|
||||||
@@ -9,10 +10,6 @@ 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)
|
||||||
},
|
},
|
||||||
@@ -127,13 +124,6 @@ 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()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-api-docs', {
|
window.app.component('lnbits-wallet-api-docs', {
|
||||||
template: '#lnbits-wallet-api-docs',
|
template: '#lnbits-wallet-api-docs',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
methods: {
|
methods: {
|
||||||
resetKeys() {
|
resetKeys() {
|
||||||
LNbits.utils
|
LNbits.utils
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-charts', {
|
window.app.component('lnbits-wallet-charts', {
|
||||||
template: '#lnbits-wallet-charts',
|
template: '#lnbits-wallet-charts',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['paymentFilter', 'chartConfig'],
|
props: ['paymentFilter', 'chartConfig'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
window.app.component('lnbits-wallet-extra', {
|
window.app.component('lnbits-wallet-extra', {
|
||||||
template: '#lnbits-wallet-extra',
|
template: '#lnbits-wallet-extra',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
props: ['chartConfig'],
|
props: ['chartConfig'],
|
||||||
computed: {
|
data() {
|
||||||
exportUrl() {
|
return {}
|
||||||
return `${window.location.origin}/wallet?usr=${this.g.user.id}&wal=${this.g.wallet.id}`
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleSendLnurl(lnurl) {
|
handleSendLnurl(lnurl) {
|
||||||
@@ -71,10 +70,6 @@ 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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-icon', {
|
window.app.component('lnbits-wallet-icon', {
|
||||||
template: '#lnbits-wallet-icon',
|
template: '#lnbits-wallet-icon',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
icon: {
|
icon: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-new', {
|
window.app.component('lnbits-wallet-new', {
|
||||||
template: '#lnbits-wallet-new',
|
template: '#lnbits-wallet-new',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
walletTypes: [{label: 'Lightning Wallet', value: 'lightning'}],
|
walletTypes: [{label: 'Lightning Wallet', value: 'lightning'}],
|
||||||
@@ -97,7 +98,6 @@ 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() {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-paylinks', {
|
window.app.component('lnbits-wallet-paylinks', {
|
||||||
template: '#lnbits-wallet-paylinks',
|
template: '#lnbits-wallet-paylinks',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
storedPaylinks: []
|
storedPaylinks: []
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.app.component('lnbits-wallet-share', {
|
window.app.component('lnbits-wallet-share', {
|
||||||
template: '#lnbits-wallet-share',
|
template: '#lnbits-wallet-share',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
computed: {
|
computed: {
|
||||||
walletApprovedShares() {
|
walletApprovedShares() {
|
||||||
return this.g.wallet.extra.shared_with.filter(
|
return this.g.wallet.extra.shared_with.filter(
|
||||||
|
|||||||
+31
-27
@@ -9,41 +9,16 @@ 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),
|
|
||||||
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', true),
|
|
||||||
// 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'),
|
||||||
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,
|
||||||
@@ -51,6 +26,35 @@ 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
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -106,11 +106,6 @@ const routes = [
|
|||||||
name: 'ExtensionsBuilder',
|
name: 'ExtensionsBuilder',
|
||||||
component: PageExtensionBuilder
|
component: PageExtensionBuilder
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/extensions/builder/preview',
|
|
||||||
name: 'ExtensionsBuilderPreview',
|
|
||||||
component: PageExtensionBuilderPreview
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/extensions',
|
path: '/extensions',
|
||||||
name: 'Extensions',
|
name: 'Extensions',
|
||||||
@@ -157,7 +152,8 @@ 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
|
||||||
@@ -172,4 +168,8 @@ window.app.use(Quasar, quasarConfig)
|
|||||||
|
|
||||||
window.app.use(window.i18n)
|
window.app.use(window.i18n)
|
||||||
window.app.use(window.router)
|
window.app.use(window.router)
|
||||||
|
|
||||||
|
window.app.component(QrcodeVue)
|
||||||
|
window.app.component('lnbits-error', window.LnbitsError)
|
||||||
|
|
||||||
window.app.mount('#vue')
|
window.app.mount('#vue')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageAccount = {
|
window.PageAccount = {
|
||||||
template: '#page-account',
|
template: '#page-account',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
untouchedUser: null,
|
untouchedUser: null,
|
||||||
@@ -39,15 +40,6 @@ window.PageAccount = {
|
|||||||
color: 'pink-3'
|
color: 'pink-3'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defaultSiteCustomisation: {
|
|
||||||
locale: 'en',
|
|
||||||
themeChoice: 'salvador',
|
|
||||||
bgimageChoice: '',
|
|
||||||
gradientChoice: true,
|
|
||||||
darkChoice: true,
|
|
||||||
borderChoice: 'hard-border',
|
|
||||||
reactionChoice: 'confettiBothSides'
|
|
||||||
},
|
|
||||||
reactionOptions: [
|
reactionOptions: [
|
||||||
'None',
|
'None',
|
||||||
'confettiBothSides',
|
'confettiBothSides',
|
||||||
@@ -222,18 +214,26 @@ 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('PATCH', '/api/v1/auth', null, {
|
const {data} = await LNbits.api.request(
|
||||||
user_id: this.g.user.id,
|
'PUT',
|
||||||
username: this.g.user.username,
|
'/api/v1/auth/update',
|
||||||
email: this.g.user.email,
|
null,
|
||||||
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({
|
||||||
@@ -610,7 +610,7 @@ window.PageAccount = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
copyAssetLinkToClipboard(asset) {
|
copyAssetLinkToClipboard(asset) {
|
||||||
const assetUrl = `${window.location.origin}/api/v1/assets/${asset.id}/data`
|
const assetUrl = `${window.location.origin}/api/v1/assets/${asset.id}/binary`
|
||||||
this.utils.copyText(assetUrl)
|
this.utils.copyText(assetUrl)
|
||||||
},
|
},
|
||||||
addUserLabel() {
|
addUserLabel() {
|
||||||
@@ -681,23 +681,6 @@ 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -711,7 +694,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.g.settings.themeOptions.includes(theme.name)
|
this.LNBITS_THEME_OPTIONS.includes(theme.name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageAdmin = {
|
window.PageAdmin = {
|
||||||
template: '#page-admin',
|
template: '#page-admin',
|
||||||
|
mixins: [windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tab: 'funding',
|
tab: 'funding',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageAudit = {
|
window.PageAudit = {
|
||||||
template: '#page-audit',
|
template: '#page-audit',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
chartsReady: false,
|
chartsReady: false,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
window.PageError = {
|
window.PageError = {
|
||||||
template: '#page-error'
|
template: '#page-error',
|
||||||
|
mixins: [window.windowMixin]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageExtensions = {
|
window.PageExtensions = {
|
||||||
template: '#page-extensions',
|
template: '#page-extensions',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extbuilderEnabled: false,
|
extbuilderEnabled: false,
|
||||||
@@ -25,63 +26,7 @@ window.PageExtensions = {
|
|||||||
selectedRelease: null,
|
selectedRelease: null,
|
||||||
uninstallAndDropDb: false,
|
uninstallAndDropDb: false,
|
||||||
maxStars: 5,
|
maxStars: 5,
|
||||||
paylinkWebsocket: null,
|
paylinkWebsocket: null
|
||||||
searchToggle: false,
|
|
||||||
reviewsUrl: null,
|
|
||||||
reviewsDialog: {
|
|
||||||
show: false,
|
|
||||||
extension: null,
|
|
||||||
loading: false,
|
|
||||||
submitting: false,
|
|
||||||
form: {
|
|
||||||
name: '',
|
|
||||||
rating: 0,
|
|
||||||
comment: ''
|
|
||||||
},
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
reviews: [],
|
|
||||||
reviewsTable: {
|
|
||||||
loading: false,
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('Name'),
|
|
||||||
field: 'name',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'comment',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('Comment'),
|
|
||||||
field: 'comment'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'created_at',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('Date'),
|
|
||||||
field: 'created_at'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'rating',
|
|
||||||
align: 'right',
|
|
||||||
label: 'Rating',
|
|
||||||
field: 'rating'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 5,
|
|
||||||
sortBy: 'created_at',
|
|
||||||
descending: true,
|
|
||||||
page: 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
paymentDialog: {
|
|
||||||
show: false,
|
|
||||||
invoice: '',
|
|
||||||
hash: ''
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -126,6 +71,7 @@ window.PageExtensions = {
|
|||||||
// the install logic has been triggered one way or another
|
// the install logic has been triggered one way or another
|
||||||
this.unsubscribeFromPaylinkWs()
|
this.unsubscribeFromPaylinkWs()
|
||||||
|
|
||||||
|
const extension = this.selectedExtension
|
||||||
this.selectedExtension.inProgress = true
|
this.selectedExtension.inProgress = true
|
||||||
this.showManageExtensionDialog = false
|
this.showManageExtensionDialog = false
|
||||||
release.payment_hash =
|
release.payment_hash =
|
||||||
@@ -339,7 +285,8 @@ window.PageExtensions = {
|
|||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
`/api/v1/extension/${extension.id}/releases`
|
`/api/v1/extension/${extension.id}/releases`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
)
|
)
|
||||||
|
|
||||||
this.selectedExtensionRepos = data.reduce((repos, release) => {
|
this.selectedExtensionRepos = data.reduce((repos, release) => {
|
||||||
@@ -377,9 +324,6 @@ window.PageExtensions = {
|
|||||||
if (!detailsLink) {
|
if (!detailsLink) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// keep a reference to the extension so we can show rating stats
|
|
||||||
this.selectedExtension =
|
|
||||||
this.extensions.find(ext => ext.id === extId) || this.selectedExtension
|
|
||||||
this.selectedExtensionDetails = null
|
this.selectedExtensionDetails = null
|
||||||
this.showExtensionDetailsDialog = true
|
this.showExtensionDetailsDialog = true
|
||||||
this.slide = 0
|
this.slide = 0
|
||||||
@@ -388,7 +332,8 @@ window.PageExtensions = {
|
|||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
`/api/v1/extension/${extId}/details?details_link=${detailsLink}`
|
`/api/v1/extension/${extId}/details?details_link=${detailsLink}`,
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
)
|
)
|
||||||
|
|
||||||
this.selectedExtensionDetails = data
|
this.selectedExtensionDetails = data
|
||||||
@@ -509,7 +454,7 @@ window.PageExtensions = {
|
|||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
`/api/v1/extension/${extId}/invoice/install`,
|
`/api/v1/extension/${extId}/invoice/install`,
|
||||||
null,
|
this.g.user.wallets[0].adminkey,
|
||||||
{
|
{
|
||||||
ext_id: extId,
|
ext_id: extId,
|
||||||
archive: release.archive,
|
archive: release.archive,
|
||||||
@@ -525,7 +470,7 @@ window.PageExtensions = {
|
|||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'PUT',
|
'PUT',
|
||||||
`/api/v1/extension/${extId}/invoice/enable`,
|
`/api/v1/extension/${extId}/invoice/enable`,
|
||||||
null,
|
this.g.user.wallets[0].adminkey,
|
||||||
{
|
{
|
||||||
amount
|
amount
|
||||||
}
|
}
|
||||||
@@ -616,7 +561,8 @@ window.PageExtensions = {
|
|||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
`/api/v1/extension/release/${org}/${repo}/${release.version}`
|
`/api/v1/extension/release/${org}/${repo}/${release.version}`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
)
|
)
|
||||||
release.loaded = true
|
release.loaded = true
|
||||||
release.is_version_compatible = data.is_version_compatible
|
release.is_version_compatible = data.is_version_compatible
|
||||||
@@ -641,13 +587,18 @@ window.PageExtensions = {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ext.inProgress = true
|
ext.inProgress = true
|
||||||
await LNbits.api.request('POST', `/api/v1/extension`, null, {
|
await LNbits.api.request(
|
||||||
ext_id: ext.id,
|
'POST',
|
||||||
archive: ext.latestRelease.archive,
|
`/api/v1/extension`,
|
||||||
source_repo: ext.latestRelease.source_repo,
|
this.g.user.wallets[0].adminkey,
|
||||||
payment_hash: ext.latestRelease.payment_hash,
|
{
|
||||||
version: ext.latestRelease.version
|
ext_id: ext.id,
|
||||||
})
|
archive: ext.latestRelease.archive,
|
||||||
|
source_repo: ext.latestRelease.source_repo,
|
||||||
|
payment_hash: ext.latestRelease.payment_hash,
|
||||||
|
version: ext.latestRelease.version
|
||||||
|
}
|
||||||
|
)
|
||||||
count++
|
count++
|
||||||
ext.isAvailable = true
|
ext.isAvailable = true
|
||||||
ext.isInstalled = true
|
ext.isInstalled = true
|
||||||
@@ -672,163 +623,6 @@ window.PageExtensions = {
|
|||||||
})
|
})
|
||||||
this.showUpdateAllDialog = false
|
this.showUpdateAllDialog = false
|
||||||
},
|
},
|
||||||
formatAvg(raw) {
|
|
||||||
const val = Number(raw || 0)
|
|
||||||
return Math.round((val / 2 / 100) * 2) / 2
|
|
||||||
},
|
|
||||||
|
|
||||||
async loadReviewStats() {
|
|
||||||
if (!this.reviewsUrl) {
|
|
||||||
console.info('Extension reviews are not configured')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/extension/reviews/tags`
|
|
||||||
)
|
|
||||||
const map = {}
|
|
||||||
data.forEach(stat => {
|
|
||||||
map[stat.tag] = stat
|
|
||||||
})
|
|
||||||
this.extensions.forEach(ext => {
|
|
||||||
ext.reviewStats = map[ext.id] || null
|
|
||||||
})
|
|
||||||
this.filterExtensions(this.searchTerm, this.tab)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async openReviews(extension) {
|
|
||||||
const targetExtension =
|
|
||||||
extension ||
|
|
||||||
(this.selectedExtensionDetails
|
|
||||||
? this.extensions.find(e => e.id === this.selectedExtensionDetails.id)
|
|
||||||
: null)
|
|
||||||
if (!targetExtension) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.reviewsUrl) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'warning',
|
|
||||||
message: this.$t('reviews_url_not_configured')
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.reviewsDialog.extension = targetExtension
|
|
||||||
this.selectedExtension = extension
|
|
||||||
this.reviewsDialog.show = true
|
|
||||||
await this.getTagReviews()
|
|
||||||
},
|
|
||||||
async getTagReviews(props) {
|
|
||||||
if (!this.reviewsUrl) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'warning',
|
|
||||||
message: this.$t('reviews_url_not_configured')
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.reviewsTable.loading = true
|
|
||||||
const params = LNbits.utils.prepareFilterQuery(this.reviewsTable, props)
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/extension/reviews/${this.selectedExtension.id}?${params}`
|
|
||||||
)
|
|
||||||
this.reviews = data.data
|
|
||||||
this.reviewsTable.pagination.rowsNumber = data.total
|
|
||||||
} catch (e) {
|
|
||||||
LNbits.utils.notifyApiError(e)
|
|
||||||
} finally {
|
|
||||||
this.reviewsTable.loading = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
formatReviewDate(val) {
|
|
||||||
if (!val) return ''
|
|
||||||
const numeric = Number(val)
|
|
||||||
if (!Number.isNaN(numeric)) {
|
|
||||||
return this.utils.formatTimestamp(numeric)
|
|
||||||
}
|
|
||||||
return this.utils.formatDate(val)
|
|
||||||
},
|
|
||||||
async submitReview() {
|
|
||||||
if (!this.reviewsDialog.extension || !this.reviewsUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.reviewsDialog.submitting = true
|
|
||||||
try {
|
|
||||||
const payload = {
|
|
||||||
tag: this.reviewsDialog.extension.id,
|
|
||||||
name: this.reviewsDialog.form.name,
|
|
||||||
rating: this.reviewsDialog.form.rating * 100,
|
|
||||||
comment: this.reviewsDialog.form.comment
|
|
||||||
}
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/reviews`,
|
|
||||||
null,
|
|
||||||
payload
|
|
||||||
)
|
|
||||||
if (data.payment_request) {
|
|
||||||
this.openInvoiceDialog(data.payment_request, data.payment_hash)
|
|
||||||
} else {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Review submitted'
|
|
||||||
})
|
|
||||||
this.resetReviewForm()
|
|
||||||
await this.getTagReviews()
|
|
||||||
await this.loadReviewStats()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
} finally {
|
|
||||||
this.reviewsDialog.submitting = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
openInvoiceDialog(invoice, hash) {
|
|
||||||
this.paymentDialog.invoice = invoice
|
|
||||||
this.paymentDialog.hash = hash
|
|
||||||
this.paymentDialog.show = true
|
|
||||||
this.listenForPayment(hash)
|
|
||||||
},
|
|
||||||
resetReviewForm() {
|
|
||||||
this.reviewsDialog.form = {
|
|
||||||
name: '',
|
|
||||||
rating: 0,
|
|
||||||
comment: ''
|
|
||||||
}
|
|
||||||
this.paymentDialog = {
|
|
||||||
show: false,
|
|
||||||
invoice: '',
|
|
||||||
hash: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
listenForPayment(hash) {
|
|
||||||
try {
|
|
||||||
const base = new URL(this.reviewsUrl)
|
|
||||||
base.protocol = base.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
||||||
base.pathname = `/api/v1/ws/${hash}`
|
|
||||||
const ws = new WebSocket(base)
|
|
||||||
ws.addEventListener('message', async () => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: this.$t('reviews_invoice_paid')
|
|
||||||
})
|
|
||||||
this.paymentDialog.show = false
|
|
||||||
this.resetReviewForm()
|
|
||||||
setTimeout(async () => {
|
|
||||||
await this.getTagReviews()
|
|
||||||
}, 1000)
|
|
||||||
|
|
||||||
await this.loadReviewStats()
|
|
||||||
ws.close()
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async fetchAllExtensions() {
|
async fetchAllExtensions() {
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request('GET', `/api/v1/extension/all`)
|
const {data} = await LNbits.api.request('GET', `/api/v1/extension/all`)
|
||||||
@@ -842,8 +636,7 @@ window.PageExtensions = {
|
|||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
this.extensions = await this.fetchAllExtensions()
|
this.extensions = await this.fetchAllExtensions()
|
||||||
this.extbuilderEnabled = this.g.user.admin || this.g.settings.extBuilder
|
this.extbuilderEnabled = this.g.user.admin || this.LNBITS_EXT_BUILDER
|
||||||
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'
|
||||||
@@ -860,8 +653,6 @@ window.PageExtensions = {
|
|||||||
this.hasNewVersion(ext)
|
this.hasNewVersion(ext)
|
||||||
)
|
)
|
||||||
|
|
||||||
await this.loadReviewStats()
|
|
||||||
|
|
||||||
this.filterExtensions(this.searchTerm, this.tab)
|
this.filterExtensions(this.searchTerm, this.tab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageExtensionBuilder = {
|
window.PageExtensionBuilder = {
|
||||||
template: '#page-extension-builder',
|
template: '#page-extension-builder',
|
||||||
|
mixins: [windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
step: 1,
|
step: 1,
|
||||||
@@ -287,10 +288,11 @@ window.PageExtensionBuilder = {
|
|||||||
`/api/v1/extension/${stub_ext_id}/releases`
|
`/api/v1/extension/${stub_ext_id}/releases`
|
||||||
)
|
)
|
||||||
|
|
||||||
this.extensionStubVersions = data
|
this.extensionStubVersions = data.sort((a, b) =>
|
||||||
const validVersions = data.filter(v => v.is_version_compatible)
|
a.version < b.version ? 1 : -1
|
||||||
this.extensionData.stub_version = validVersions[0]
|
)
|
||||||
? validVersions[0].version
|
this.extensionData.stub_version = this.extensionStubVersions[0]
|
||||||
|
? this.extensionStubVersions[0].version
|
||||||
: ''
|
: ''
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
@@ -309,20 +311,7 @@ window.PageExtensionBuilder = {
|
|||||||
iframeDoc.body.style.transform = 'scale(0.8)'
|
iframeDoc.body.style.transform = 'scale(0.8)'
|
||||||
iframeDoc.body.style.transformOrigin = 'center top'
|
iframeDoc.body.style.transformOrigin = 'center top'
|
||||||
}
|
}
|
||||||
|
iframe.src = `/extensions/builder/preview/${this.extensionData.id}?page_name=${previewPageName}`
|
||||||
let componentName =
|
|
||||||
'Page' +
|
|
||||||
this.extensionData.id
|
|
||||||
.toLowerCase()
|
|
||||||
.split('_')
|
|
||||||
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
||||||
.join('')
|
|
||||||
if (previewPageName === 'public_page') componentName += 'Public'
|
|
||||||
iframe.src =
|
|
||||||
`/extensions/builder/preview?` +
|
|
||||||
`ext_id=${this.extensionData.id}` +
|
|
||||||
`&page=${previewPageName}` +
|
|
||||||
`&component=${componentName}`
|
|
||||||
},
|
},
|
||||||
initBasicData() {
|
initBasicData() {
|
||||||
this.extensionData.owner_data.fields = [
|
this.extensionData.owner_data.fields = [
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
window.PageExtensionBuilderPreview = {
|
|
||||||
template: '#page-extension-builder-preview',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
watch: {
|
|
||||||
name: 'reload'
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
extId: '',
|
|
||||||
pageName: '',
|
|
||||||
componentName: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async reload() {
|
|
||||||
await LNbits.utils.loadTemplate(
|
|
||||||
`/extensions/builder/preview/${this.extId}/template?page_name=${this.pageName}`
|
|
||||||
)
|
|
||||||
await LNbits.utils.loadScript(
|
|
||||||
`/extensions/builder/preview/${this.extId}/component?page_name=${this.pageName}`
|
|
||||||
)
|
|
||||||
|
|
||||||
this._component = window[this.componentName]
|
|
||||||
console.log(
|
|
||||||
'LNbits preview reloaded componentName:',
|
|
||||||
this.componentName,
|
|
||||||
!!this._component
|
|
||||||
)
|
|
||||||
this.$forceUpdate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
|
||||||
this.extId = urlParams.get('ext_id') || ''
|
|
||||||
this.pageName = urlParams.get('page') || ''
|
|
||||||
this.componentName = urlParams.get('component') || ''
|
|
||||||
|
|
||||||
await this.reload()
|
|
||||||
},
|
|
||||||
render() {
|
|
||||||
if (this._component) {
|
|
||||||
return Vue.h(this._component)
|
|
||||||
}
|
|
||||||
return Vue.h('div', 'Loading...')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageFirstInstall = {
|
window.PageFirstInstall = {
|
||||||
template: '#page-first-install',
|
template: '#page-first-install',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loginData: {
|
loginData: {
|
||||||
@@ -7,8 +8,7 @@ window.PageFirstInstall = {
|
|||||||
isPwdRepeat: true,
|
isPwdRepeat: true,
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordRepeat: '',
|
passwordRepeat: ''
|
||||||
firstInstallToken: ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -18,25 +18,20 @@ window.PageFirstInstall = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
setPassword() {
|
async setPassword() {
|
||||||
LNbits.api
|
try {
|
||||||
.request('PUT', `/api/v1/auth/first_install`, null, {
|
await LNbits.api.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
|
|
||||||
})
|
})
|
||||||
.then(async () => {
|
window.location.href = '/admin'
|
||||||
const res = await LNbits.api.getAuthUser()
|
} catch (e) {
|
||||||
this.g.user = LNbits.map.user(res.data)
|
LNbits.utils.notifyApiError(e)
|
||||||
this.g.isPublicPage = false
|
}
|
||||||
this.$router.push('/admin')
|
|
||||||
})
|
|
||||||
.catch(this.utils.notifyApiError)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
const params = new URLSearchParams(window.location.search)
|
document.title = 'First Install - LNbits'
|
||||||
this.loginData.firstInstallToken = params.get('token') || ''
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageHome = {
|
window.PageHome = {
|
||||||
template: '#page-home',
|
template: '#page-home',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
lnurl: '',
|
lnurl: '',
|
||||||
@@ -11,7 +12,6 @@ window.PageHome = {
|
|||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
passwordRepeat: '',
|
passwordRepeat: '',
|
||||||
invitationCode: '',
|
|
||||||
walletName: '',
|
walletName: '',
|
||||||
signup: false
|
signup: false
|
||||||
}
|
}
|
||||||
@@ -20,15 +20,40 @@ window.PageHome = {
|
|||||||
showClaimLnurl() {
|
showClaimLnurl() {
|
||||||
return (
|
return (
|
||||||
this.lnurl !== '' &&
|
this.lnurl !== '' &&
|
||||||
this.g.settings.allowRegister &&
|
this.allowRegister &&
|
||||||
'user-id-only' in this.g.settings.authMethods
|
'user-id-only' in this.LNBITS_AUTH_METHODS
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
formatDescription() {
|
formatDescription() {
|
||||||
return LNbits.utils.convertMarkdown(this.g.settings.siteDescription)
|
return LNbits.utils.convertMarkdown(this.SITE_DESCRIPTION)
|
||||||
},
|
},
|
||||||
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: {
|
||||||
@@ -41,7 +66,6 @@ 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
|
||||||
@@ -53,8 +77,7 @@ 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) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageNodePublic = {
|
window.PageNodePublic = {
|
||||||
template: '#page-node-public',
|
template: '#page-node-public',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
window.PageNode = {
|
window.PageNode = {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#page-node',
|
template: '#page-node',
|
||||||
config: {
|
config: {
|
||||||
globalProperties: {
|
globalProperties: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PagePayments = {
|
window.PagePayments = {
|
||||||
template: '#page-payments',
|
template: '#page-payments',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
payments: [],
|
payments: [],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageUsers = {
|
window.PageUsers = {
|
||||||
template: '#page-users',
|
template: '#page-users',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
paymentsWallet: {},
|
paymentsWallet: {},
|
||||||
@@ -70,10 +71,10 @@ window.PageUsers = {
|
|||||||
usersTable: {
|
usersTable: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'activated',
|
name: 'admin',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: this.$t('activated'),
|
label: 'Admin',
|
||||||
field: 'activated',
|
field: 'admin',
|
||||||
sortable: false
|
sortable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -401,7 +402,7 @@ window.PageUsers = {
|
|||||||
|
|
||||||
toggleAdmin(userId) {
|
toggleAdmin(userId) {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('PUT', `/users/api/v1/user/${userId}/admin`)
|
.request('GET', `/users/api/v1/user/${userId}/admin`)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.fetchUsers()
|
this.fetchUsers()
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
@@ -412,19 +413,6 @@ 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
|
||||||
@@ -451,20 +439,6 @@ window.PageUsers = {
|
|||||||
this.activeUser.show = false
|
this.activeUser.show = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async impersonateUser(user_id) {
|
|
||||||
try {
|
|
||||||
await LNbits.api.impersonateUser(user_id)
|
|
||||||
LNbits.utils.backupLocalStorage('impersonation', true)
|
|
||||||
this.$q.localStorage.setItem('lnbits.disclaimerShown', true)
|
|
||||||
window.location = '/wallet'
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Failed to impersonate user!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async showWalletPayments(walletId) {
|
async showWalletPayments(walletId) {
|
||||||
this.activeUser.show = false
|
this.activeUser.show = false
|
||||||
await this.fetchWallets(this.users[0].id)
|
await this.fetchWallets(this.users[0].id)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageWallet = {
|
window.PageWallet = {
|
||||||
template: '#page-wallet',
|
template: '#page-wallet',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
parse: {
|
parse: {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
window.PageWallets = {
|
window.PageWallets = {
|
||||||
template: '#page-wallets',
|
template: '#page-wallets',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: null,
|
user: null,
|
||||||
|
|||||||
+13
-58
@@ -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.g.settings.cacheKey)
|
_url.searchParams.set('v', WINDOW_SETTINGS.CACHE_KEY)
|
||||||
return _url.toString()
|
return _url.toString()
|
||||||
},
|
},
|
||||||
loadScript(src) {
|
loadScript(src) {
|
||||||
@@ -67,29 +67,6 @@ window._lnbitsUtils = {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
backupLocalStorage(backupKey, cleanup = false) {
|
|
||||||
const lnbitsEntries =
|
|
||||||
Object.entries(Quasar.LocalStorage.getAll()).filter(
|
|
||||||
([k, v]) => k.startsWith('lnbits.') && k !== `lnbits.${backupKey}`
|
|
||||||
) || []
|
|
||||||
|
|
||||||
Quasar.LocalStorage.setItem(`lnbits.${backupKey}`, lnbitsEntries)
|
|
||||||
if (cleanup) {
|
|
||||||
lnbitsEntries.forEach(([k, v]) => Quasar.LocalStorage.remove(k))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
restoreLocalStorage(backupKey) {
|
|
||||||
Object.entries(Quasar.LocalStorage.getAll())
|
|
||||||
.filter(
|
|
||||||
([k, v]) => k.startsWith('lnbits.') && k !== `lnbits.${backupKey}`
|
|
||||||
)
|
|
||||||
.forEach(([k, v]) => Quasar.LocalStorage.remove(k))
|
|
||||||
|
|
||||||
const lnbitsEntries =
|
|
||||||
Quasar.LocalStorage.getItem(`lnbits.${backupKey}`) || []
|
|
||||||
lnbitsEntries.forEach(([k, v]) => Quasar.LocalStorage.setItem(k, v))
|
|
||||||
Quasar.LocalStorage.remove(`lnbits.${backupKey}`)
|
|
||||||
},
|
|
||||||
async digestMessage(message) {
|
async digestMessage(message) {
|
||||||
const msgUint8 = new TextEncoder().encode(message)
|
const msgUint8 = new TextEncoder().encode(message)
|
||||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
|
const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8)
|
||||||
@@ -137,14 +114,7 @@ window._lnbitsUtils = {
|
|||||||
formatMsat(value) {
|
formatMsat(value) {
|
||||||
return this.formatSat(value / 1000)
|
return this.formatSat(value / 1000)
|
||||||
},
|
},
|
||||||
parseJSONSafe(str) {
|
notifyApiError(error) {
|
||||||
try {
|
|
||||||
return JSON.parse(str)
|
|
||||||
} catch (e) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async notifyApiError(error) {
|
|
||||||
if (!error.response) {
|
if (!error.response) {
|
||||||
return console.error(error)
|
return console.error(error)
|
||||||
}
|
}
|
||||||
@@ -153,32 +123,17 @@ window._lnbitsUtils = {
|
|||||||
401: 'warning',
|
401: 'warning',
|
||||||
500: 'negative'
|
500: 'negative'
|
||||||
}
|
}
|
||||||
let messages = error.response.data.detail
|
Quasar.Notify.create({
|
||||||
if (!messages) {
|
timeout: 5000,
|
||||||
const text = await error.response.data?.text()
|
type: types[error.response.status] || 'warning',
|
||||||
messages = this.parseJSONSafe(text)?.detail
|
message:
|
||||||
}
|
error.response.data.message || error.response.data.detail || null,
|
||||||
if (messages) {
|
caption:
|
||||||
messages = Array.isArray(messages)
|
[error.response.status, ' ', error.response.statusText]
|
||||||
? messages.map(e => e.msg + ` (${e.loc?.join('/')})`)
|
.join('')
|
||||||
: (messages = [messages])
|
.toUpperCase() || null,
|
||||||
} else {
|
icon: null
|
||||||
messages = [error.response.data.message || error.response.data.detail]
|
})
|
||||||
}
|
|
||||||
|
|
||||||
messages.forEach(message =>
|
|
||||||
Quasar.Notify.create({
|
|
||||||
timeout: 5000,
|
|
||||||
type: types[error.response.status] || 'warning',
|
|
||||||
message,
|
|
||||||
caption:
|
|
||||||
[error.response.status, ' ', error.response.statusText]
|
|
||||||
.join('')
|
|
||||||
.toUpperCase() || null,
|
|
||||||
icon: null,
|
|
||||||
closeBtn: true
|
|
||||||
})
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
search(data, q, field, separator) {
|
search(data, q, field, separator) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -45,8 +45,6 @@
|
|||||||
"js/pages/error.js",
|
"js/pages/error.js",
|
||||||
"js/pages/home.js",
|
"js/pages/home.js",
|
||||||
"js/pages/extensions_builder.js",
|
"js/pages/extensions_builder.js",
|
||||||
"js/pages/extensions_builder.js",
|
|
||||||
"js/pages/extensions_builder_preview.js",
|
|
||||||
"js/pages/extensions.js",
|
"js/pages/extensions.js",
|
||||||
"js/pages/first-install.js",
|
"js/pages/first-install.js",
|
||||||
"js/pages/payments.js",
|
"js/pages/payments.js",
|
||||||
@@ -78,7 +76,6 @@
|
|||||||
"js/components/lnbits-wallet-paylinks.js",
|
"js/components/lnbits-wallet-paylinks.js",
|
||||||
"js/components/lnbits-wallet-extra.js",
|
"js/components/lnbits-wallet-extra.js",
|
||||||
"js/components/lnbits-home-logos.js",
|
"js/components/lnbits-home-logos.js",
|
||||||
"js/components/lnbits-error.js",
|
|
||||||
"js/components/lnbits-qrcode.js",
|
"js/components/lnbits-qrcode.js",
|
||||||
"js/components/lnbits-qrcode-lnurl.js",
|
"js/components/lnbits-qrcode-lnurl.js",
|
||||||
"js/components/lnbits-disclaimer.js",
|
"js/components/lnbits-disclaimer.js",
|
||||||
|
|||||||
+1
-5
@@ -178,11 +178,7 @@ 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
|
||||||
|
|
||||||
from lnbits.core.services.payments import check_payment_status
|
status = await payment.check_status(skip_internal_payment_notifications=True)
|
||||||
|
|
||||||
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 SETTINGS = {{ SETTINGS | tojson | safe }}
|
const WINDOW_SETTINGS = {{ WINDOW_SETTINGS | tojson }}
|
||||||
const CURRENCIES = {{ CURRENCIES | tojson | safe }}
|
Object.keys(WINDOW_SETTINGS).forEach(key => {
|
||||||
// backwards compatibility for extensions (tpos)
|
window[key] = WINDOW_SETTINGS[key]
|
||||||
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') %}
|
||||||
@@ -94,6 +94,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<!-- scripts from extensions -->
|
<!-- scripts from extensions -->
|
||||||
{% block scripts %}{% endblock %}
|
{% block scripts %}{% endblock %}
|
||||||
|
<!-- modules js -->
|
||||||
|
<script src="/static/modules/components.umd.js"></script>
|
||||||
<!-- components js -->
|
<!-- components js -->
|
||||||
{% for url in INCLUDED_COMPONENTS %}
|
{% for url in INCLUDED_COMPONENTS %}
|
||||||
<script src="{{ static_url_for('static', url) }}"></script>
|
<script src="{{ static_url_for('static', url) }}"></script>
|
||||||
|
|||||||
+22
-118
@@ -29,14 +29,13 @@ include('components/lnbits-wallet-api-docs.vue') %} {%
|
|||||||
include('components/lnbits-wallet-share.vue') %} {%
|
include('components/lnbits-wallet-share.vue') %} {%
|
||||||
include('components/lnbits-wallet-charts.vue') %} {%
|
include('components/lnbits-wallet-charts.vue') %} {%
|
||||||
include('components/lnbits-wallet-paylinks.vue') %} {%
|
include('components/lnbits-wallet-paylinks.vue') %} {%
|
||||||
include('components/lnbits-wallet-extra.vue') %} {%
|
include('components/lnbits-wallet-extra.vue') %}
|
||||||
include('components/lnbits-error.vue') %}
|
|
||||||
|
|
||||||
<template id="lnbits-manage">
|
<template id="lnbits-manage">
|
||||||
<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="g.settings.showAdmin" to="/admin">
|
<q-item v-if="showAdmin" to="/admin">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="settings"
|
name="settings"
|
||||||
@@ -51,7 +50,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="g.settings.showNodemanager" to="/node">
|
<q-item v-if="showNode" to="/node">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="developer_board"
|
name="developer_board"
|
||||||
@@ -66,7 +65,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="g.settings.showAdmin" to="/users">
|
<q-item v-if="showUsers" to="/users">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="groups"
|
name="groups"
|
||||||
@@ -81,7 +80,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="g.settings.showAudit" to="/audit">
|
<q-item v-if="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 +111,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="g.settings.showExtensions" to="/extensions">
|
<q-item v-if="showExtensions" to="/extensions">
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="extension"
|
name="extension"
|
||||||
@@ -561,32 +560,11 @@ include('components/lnbits-error.vue') %}
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="lnbits-extension-rating">
|
<template id="lnbits-extension-rating">
|
||||||
<div class="row items-center q-gutter-xs" style="margin-bottom: 3px">
|
<div style="margin-bottom: 3px">
|
||||||
<q-rating
|
<q-rating v-model="rating" size="1.5em" :max="5" color="primary"
|
||||||
:model-value="displayRating"
|
><q-tooltip>
|
||||||
size="1.4em"
|
<span v-text="$t('extension_rating_soon')"></span> </q-tooltip
|
||||||
:max="5"
|
></q-rating>
|
||||||
color="primary"
|
|
||||||
icon="star_border"
|
|
||||||
icon-selected="star"
|
|
||||||
icon-half="star_half"
|
|
||||||
readonly
|
|
||||||
:class="clickable ? 'cursor-pointer' : ''"
|
|
||||||
@click="handleClick"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="!hasData">
|
|
||||||
<span v-text="$t('extension_rating_soon')"></span>
|
|
||||||
</q-tooltip>
|
|
||||||
<q-tooltip v-else-if="clickable">
|
|
||||||
<span v-text="$t('reviews_open')"></span>
|
|
||||||
</q-tooltip>
|
|
||||||
</q-rating>
|
|
||||||
<div
|
|
||||||
class="text-caption text-grey"
|
|
||||||
v-if="count !== null && count !== undefined"
|
|
||||||
>
|
|
||||||
<span v-text="`(${count})`"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -686,7 +664,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
dense
|
dense
|
||||||
filled
|
filled
|
||||||
v-model="walletName"
|
v-model="walletName"
|
||||||
:label="$t('name_your_wallet', {name: g.settings.siteTitle + ' *'})"
|
:label="$t('name_your_wallet', {name: SITE_TITLE + ' *'})"
|
||||||
></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,90 +792,16 @@ 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="disableRegister"
|
:disable="
|
||||||
|
!password ||
|
||||||
|
!passwordRepeat ||
|
||||||
|
!username ||
|
||||||
|
password !== passwordRepeat
|
||||||
|
"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
:label="$t('create_account')"
|
:label="$t('create_account')"
|
||||||
@@ -1034,9 +938,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="
|
||||||
g.settings.keycloakIcon
|
keycloakIcon
|
||||||
? g.settings.keycloakIcon
|
? keycloakIcon
|
||||||
: utils.url_for('lnbits/static/images/keycloak-logo.png')
|
: 'lnbits/static/images/keycloak-logo.png'
|
||||||
"
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
@@ -1044,7 +948,7 @@ include('components/lnbits-error.vue') %}
|
|||||||
<span
|
<span
|
||||||
v-text="
|
v-text="
|
||||||
$t('signin_with_custom_org', {
|
$t('signin_with_custom_org', {
|
||||||
custom_org: g.settings.keycloakOrg || 'Keycloak'
|
custom_org: keycloakOrg
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
></span>
|
></span>
|
||||||
|
|||||||
@@ -137,19 +137,6 @@
|
|||||||
: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">
|
|
||||||
<p>
|
|
||||||
<span v-text="$t('reviews_url')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
v-model="formData.lnbits_extensions_reviews_url"
|
|
||||||
:label="$t('reviews_url_label')"
|
|
||||||
:hint="$t('reviews_url_hint')"
|
|
||||||
type="url"
|
|
||||||
autocomplete="off"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div v-if="g.settings.hasNodemanager">
|
<div v-if="LNBITS_NODE_UI">
|
||||||
<p>
|
<p>
|
||||||
<span v-text="$t('node_management')"></span>
|
<span v-text="$t('node_management')"></span>
|
||||||
</p>
|
</p>
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
v-model="formData.lnbits_node_ui_transactions"
|
v-model="formData.lnbits_node_ui_transactions"
|
||||||
></q-toggle>
|
></q-toggle>
|
||||||
</div>
|
</div>
|
||||||
<p v-if="!g.settings.hasNodemanager">
|
<p v-if="!LNBITS_NODE_UI">
|
||||||
<span v-text="$t('node_management_not_supported')"></span>
|
<span v-text="$t('node_management_not_supported')"></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -86,42 +86,6 @@
|
|||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
|
||||||
<div class="row q-col-gutter-md">
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<p>
|
|
||||||
<span v-text="$t('wallet_featured_button_label')"></span>
|
|
||||||
</p>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
type="text"
|
|
||||||
v-model="formData.lnbits_wallet_featured_button_label"
|
|
||||||
label="Loop to Onchain"
|
|
||||||
:hint="$t('wallet_featured_button_label_hint')"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<p><span v-text="$t('wallet_featured_button_url')"></span></p>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
type="text"
|
|
||||||
v-model="formData.lnbits_wallet_featured_button_url"
|
|
||||||
label="/boltz"
|
|
||||||
:hint="$t('wallet_featured_button_url_hint')"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4">
|
|
||||||
<p><span v-text="$t('wallet_featured_button_icon')"></span></p>
|
|
||||||
<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-6">
|
||||||
<p><span v-text="$t('ui_custom_badge')"></span></p>
|
<p><span v-text="$t('ui_custom_badge')"></span></p>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
>
|
>
|
||||||
</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>
|
||||||
@@ -56,10 +57,7 @@
|
|||||||
>
|
>
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<br />
|
||||||
</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>
|
||||||
@@ -78,237 +76,8 @@
|
|||||||
/>
|
/>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
<br />
|
||||||
<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>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user