Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c820456500 | ||
|
|
5714848434 | ||
|
|
f87e5d4372 | ||
|
|
a0502f1676 | ||
|
|
f6c8a308dc | ||
|
|
6df3933c1b | ||
|
|
ffef085fa7 | ||
|
|
913991c72f | ||
|
|
6f611461f2 | ||
|
|
681385e2a2 | ||
|
|
883d52c303 | ||
|
|
1058461882 | ||
|
|
3a7a88857a | ||
|
|
6834b5e00f | ||
|
|
10093bb465 | ||
|
|
1323a2005b | ||
|
|
bafb4ddf75 | ||
|
|
aa050eaf49 | ||
|
|
0c76efa2b9 | ||
|
|
681730a4af | ||
|
|
63adcb6780 | ||
|
|
35f7821183 | ||
|
|
b185d9585c | ||
|
|
30a8d88ada | ||
|
|
f7e984198b | ||
|
|
eb09e95e88 | ||
|
|
a883e2c7d8 | ||
|
|
e06abb52ce | ||
|
|
9f64b5349d | ||
|
|
a6bbddce41 | ||
|
|
adb9d24673 | ||
|
|
dc85f26964 | ||
|
|
14153b4a8f | ||
|
|
b8a5f3942c |
@@ -3,7 +3,7 @@ name: release-rc
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- "*-rc[0-9]"
|
- "*-rc[0-9]+"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ lnbits/static/bundle.min.css.old
|
|||||||
lnbits/static/bundle-components.min.js.old
|
lnbits/static/bundle-components.min.js.old
|
||||||
lnbits/upgrades
|
lnbits/upgrades
|
||||||
docker
|
docker
|
||||||
|
generated
|
||||||
|
openapi.json*
|
||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
*result*
|
*result*
|
||||||
|
|||||||
@@ -99,9 +99,13 @@ openapi:
|
|||||||
curl -s http://0.0.0.0:5003/openapi.json | poetry run openapi-spec-validator --errors=all -
|
curl -s http://0.0.0.0:5003/openapi.json | poetry run openapi-spec-validator --errors=all -
|
||||||
# kill -9 %1
|
# kill -9 %1
|
||||||
|
|
||||||
bak:
|
generate:
|
||||||
# LNBITS_DATABASE_URL=postgres://postgres:postgres@0.0.0.0:5432/postgres
|
rm -rf generated
|
||||||
#
|
mkdir generated
|
||||||
|
rm -f openapi.json
|
||||||
|
wget localhost:5000/openapi.json
|
||||||
|
npm run generate
|
||||||
|
rm openapi.json
|
||||||
|
|
||||||
sass:
|
sass:
|
||||||
npm run sass
|
npm run sass
|
||||||
|
|||||||
@@ -29,11 +29,13 @@ It is recommended to use the latest version of Poetry. Make sure you have Python
|
|||||||
|
|
||||||
### Install Python 3.12
|
### Install Python 3.12
|
||||||
|
|
||||||
|
## Option 2 (recommended): Poetry
|
||||||
|
|
||||||
|
It is recommended to use the latest version of Poetry. Make sure you have Python version 3.9 or higher installed.
|
||||||
|
|
||||||
|
### Verify Python version
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
|
||||||
sudo apt update -y
|
|
||||||
sudo apt install -y python3.12 python3.12-dev # ensure correct headers needed for secp256k1
|
|
||||||
sudo apt install -y pkg-config python3-dev build-essential # ensure correct headers
|
|
||||||
python3 --version
|
python3 --version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -83,6 +85,18 @@ poetry install --only main
|
|||||||
# Start LNbits with `poetry run lnbits`
|
# Start LNbits with `poetry run lnbits`
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Option 2: Install script (on Debian/Ubuntu)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
wget https://raw.githubusercontent.com/lnbits/lnbits/main/lnbits.sh &&
|
||||||
|
chmod +x lnbits.sh &&
|
||||||
|
./lnbits.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Now visit `0.0.0.0:5000` to make a super-user account.
|
||||||
|
|
||||||
|
`./lnbits.sh` can be used to run, but for more control `cd lnbits` and use `poetry run lnbits` (see previous option).
|
||||||
|
|
||||||
## Option 3: Nix
|
## Option 3: Nix
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Check install has not already run
|
||||||
|
if [ ! -d lnbits/data ]; then
|
||||||
|
|
||||||
|
# Update package list and install prerequisites non-interactively
|
||||||
|
sudo apt update -y
|
||||||
|
sudo apt install -y software-properties-common
|
||||||
|
|
||||||
|
# Add the deadsnakes PPA repository non-interactively
|
||||||
|
sudo add-apt-repository -y ppa:deadsnakes/ppa
|
||||||
|
|
||||||
|
# Install Python 3.9 and distutils non-interactively
|
||||||
|
sudo apt install -y python3.9 python3.9-distutils
|
||||||
|
|
||||||
|
# Install Poetry
|
||||||
|
curl -sSL https://install.python-poetry.org | python3.9 -
|
||||||
|
|
||||||
|
# Add Poetry to PATH for the current session
|
||||||
|
export PATH="/home/$USER/.local/bin:$PATH"
|
||||||
|
|
||||||
|
if [ ! -d lnbits/wallets ]; then
|
||||||
|
# Clone the LNbits repository
|
||||||
|
git clone https://github.com/lnbits/lnbits.git
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Failed to clone the repository ... FAIL"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Ensure we are in the lnbits directory
|
||||||
|
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
git checkout main
|
||||||
|
# Make data folder
|
||||||
|
mkdir data
|
||||||
|
|
||||||
|
# Copy the .env.example to .env
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
elif [ ! -d lnbits/wallets ]; then
|
||||||
|
# cd into lnbits
|
||||||
|
cd lnbits || { echo "Failed to cd into lnbits ... FAIL"; exit 1; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install the dependencies using Poetry
|
||||||
|
poetry env use python3.9
|
||||||
|
poetry install --only main
|
||||||
|
|
||||||
|
# Set environment variables for LNbits
|
||||||
|
export LNBITS_ADMIN_UI=true
|
||||||
|
export HOST=0.0.0.0
|
||||||
|
|
||||||
|
# Run LNbits
|
||||||
|
poetry run lnbits
|
||||||
+7
-3
@@ -163,9 +163,13 @@ def create_app() -> FastAPI:
|
|||||||
core_app_extra.register_new_ratelimiter = register_new_ratelimiter(app)
|
core_app_extra.register_new_ratelimiter = register_new_ratelimiter(app)
|
||||||
|
|
||||||
# register static files
|
# register static files
|
||||||
static_path = Path("lnbits", "static")
|
app.mount("/static", StaticFiles(directory=Path("lnbits", "static")), name="static")
|
||||||
static = StaticFiles(directory=static_path)
|
Path(settings.lnbits_data_folder, "images").mkdir(parents=True, exist_ok=True)
|
||||||
app.mount("/static", static, name="static")
|
app.mount(
|
||||||
|
"/library",
|
||||||
|
StaticFiles(directory=Path(settings.lnbits_data_folder, "images")),
|
||||||
|
name="library",
|
||||||
|
)
|
||||||
|
|
||||||
g().base_url = f"http://{settings.host}:{settings.port}"
|
g().base_url = f"http://{settings.host}:{settings.port}"
|
||||||
|
|
||||||
|
|||||||
+37
-14
@@ -5,6 +5,7 @@ import time
|
|||||||
from functools import wraps
|
from functools import wraps
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import click
|
import click
|
||||||
import httpx
|
import httpx
|
||||||
@@ -27,13 +28,13 @@ from lnbits.core.crud import (
|
|||||||
update_payment,
|
update_payment,
|
||||||
)
|
)
|
||||||
from lnbits.core.helpers import is_valid_url, migrate_databases
|
from lnbits.core.helpers import is_valid_url, migrate_databases
|
||||||
from lnbits.core.models import Payment, PaymentState
|
from lnbits.core.models import Account, Payment, PaymentState
|
||||||
from lnbits.core.models.extensions import (
|
from lnbits.core.models.extensions import (
|
||||||
CreateExtension,
|
CreateExtension,
|
||||||
ExtensionRelease,
|
ExtensionRelease,
|
||||||
InstallableExtension,
|
InstallableExtension,
|
||||||
)
|
)
|
||||||
from lnbits.core.services import check_admin_settings
|
from lnbits.core.services import check_admin_settings, create_user_account_no_ckeck
|
||||||
from lnbits.core.views.extension_api import (
|
from lnbits.core.views.extension_api import (
|
||||||
api_install_extension,
|
api_install_extension,
|
||||||
api_uninstall_extension,
|
api_uninstall_extension,
|
||||||
@@ -64,6 +65,13 @@ def db():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@lnbits_cli.group()
|
||||||
|
def users():
|
||||||
|
"""
|
||||||
|
Users related commands
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
@lnbits_cli.group()
|
@lnbits_cli.group()
|
||||||
def extensions():
|
def extensions():
|
||||||
"""
|
"""
|
||||||
@@ -109,7 +117,7 @@ async def delete_settings():
|
|||||||
"""Deletes the settings"""
|
"""Deletes the settings"""
|
||||||
|
|
||||||
async with core_db.connect() as conn:
|
async with core_db.connect() as conn:
|
||||||
await conn.execute("DELETE from settings")
|
await conn.execute("DELETE from system_settings")
|
||||||
|
|
||||||
|
|
||||||
@db.command("migrate")
|
@db.command("migrate")
|
||||||
@@ -180,17 +188,6 @@ async def database_revert_payment(checking_id: str):
|
|||||||
click.echo(f"Payment '{checking_id}' marked as pending.")
|
click.echo(f"Payment '{checking_id}' marked as pending.")
|
||||||
|
|
||||||
|
|
||||||
@db.command("cleanup-accounts")
|
|
||||||
@click.argument("days", type=int, required=False)
|
|
||||||
@coro
|
|
||||||
async def database_cleanup_accounts(days: Optional[int] = None):
|
|
||||||
"""Delete all accounts that have no wallets"""
|
|
||||||
async with core_db.connect() as conn:
|
|
||||||
delta = days or settings.cleanup_wallets_days
|
|
||||||
delta = delta * 24 * 60 * 60
|
|
||||||
await delete_accounts_no_wallets(delta, conn)
|
|
||||||
|
|
||||||
|
|
||||||
@db.command("check-payments")
|
@db.command("check-payments")
|
||||||
@click.option("-d", "--days", help="Maximum age of payments in days.")
|
@click.option("-d", "--days", help="Maximum age of payments in days.")
|
||||||
@click.option("-l", "--limit", help="Maximum number of payments to be checked.")
|
@click.option("-l", "--limit", help="Maximum number of payments to be checked.")
|
||||||
@@ -271,6 +268,32 @@ async def check_invalid_payments(
|
|||||||
click.echo(" ".join([w, str(data[0]), str(data[1] / 1000).ljust(10)]))
|
click.echo(" ".join([w, str(data[0]), str(data[1] / 1000).ljust(10)]))
|
||||||
|
|
||||||
|
|
||||||
|
@users.command("new")
|
||||||
|
@click.option("-u", "--username", required=True, help="Username.")
|
||||||
|
@click.option("-p", "--password", required=True, help="Password.")
|
||||||
|
@coro
|
||||||
|
async def create_user(username: str, password: str):
|
||||||
|
"""Create a new user bypassing the system 'new_accounts_allowed' rules"""
|
||||||
|
account = Account(
|
||||||
|
id=uuid4().hex,
|
||||||
|
username=username,
|
||||||
|
)
|
||||||
|
account.hash_password(password)
|
||||||
|
user = await create_user_account_no_ckeck(account)
|
||||||
|
click.echo(f"User '{user.username}' created. Id: '{user.id}'")
|
||||||
|
|
||||||
|
|
||||||
|
@users.command("cleanup-accounts")
|
||||||
|
@click.argument("days", type=int, required=False)
|
||||||
|
@coro
|
||||||
|
async def database_cleanup_accounts(days: Optional[int] = None):
|
||||||
|
"""Delete all accounts that have no wallets"""
|
||||||
|
async with core_db.connect() as conn:
|
||||||
|
delta = days or settings.cleanup_wallets_days
|
||||||
|
delta = delta * 24 * 60 * 60
|
||||||
|
await delete_accounts_no_wallets(delta, conn)
|
||||||
|
|
||||||
|
|
||||||
@extensions.command("list")
|
@extensions.command("list")
|
||||||
@coro
|
@coro
|
||||||
async def extensions_list():
|
async def extensions_list():
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from time import time
|
from time import time
|
||||||
from typing import Optional, Tuple
|
from typing import Any, Optional, Tuple
|
||||||
|
|
||||||
from lnbits.core.crud.wallets import get_total_balance, get_wallet
|
from lnbits.core.crud.wallets import get_total_balance, get_wallet
|
||||||
from lnbits.core.db import db
|
from lnbits.core.db import db
|
||||||
@@ -110,8 +110,7 @@ async def get_payments_paginated(
|
|||||||
- complete | pending | failed | outgoing | incoming.
|
- complete | pending | failed | outgoing | incoming.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
values: dict = {
|
values: dict[str, Any] = {
|
||||||
"wallet_id": wallet_id,
|
|
||||||
"time": since,
|
"time": since,
|
||||||
}
|
}
|
||||||
clause: list[str] = []
|
clause: list[str] = []
|
||||||
@@ -120,6 +119,7 @@ async def get_payments_paginated(
|
|||||||
clause.append(f"time > {db.timestamp_placeholder('time')}")
|
clause.append(f"time > {db.timestamp_placeholder('time')}")
|
||||||
|
|
||||||
if wallet_id:
|
if wallet_id:
|
||||||
|
values["wallet_id"] = wallet_id
|
||||||
clause.append("wallet_id = :wallet_id")
|
clause.append("wallet_id = :wallet_id")
|
||||||
|
|
||||||
if complete and pending:
|
if complete and pending:
|
||||||
|
|||||||
@@ -46,3 +46,8 @@ class SimpleItem(BaseModel):
|
|||||||
class DbVersion(BaseModel):
|
class DbVersion(BaseModel):
|
||||||
db: str
|
db: str
|
||||||
version: int
|
version: int
|
||||||
|
|
||||||
|
|
||||||
|
class Image(BaseModel):
|
||||||
|
filename: str
|
||||||
|
directory: str = "library"
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ NOTIFICATION_TEMPLATES = {
|
|||||||
*In/Out payments*: `{in_payments_count}`/`{out_payments_count}`.
|
*In/Out payments*: `{in_payments_count}`/`{out_payments_count}`.
|
||||||
*Pending payments*: `{pending_payments_count}`.
|
*Pending payments*: `{pending_payments_count}`.
|
||||||
*Failed payments*: `{failed_payments_count}`.
|
*Failed payments*: `{failed_payments_count}`.
|
||||||
*LNbits balance*: `{lnbits_balance_sats}` sats.""",
|
*LNbits balance*: `{lnbits_balance_sats}` sats.
|
||||||
|
*Node balance*: `{node_balance_sats}` sats.""",
|
||||||
"server_start_stop": """*SERVER*
|
"server_start_stop": """*SERVER*
|
||||||
{message}
|
{message}
|
||||||
*Time*: `{up_time}` seconds.
|
*Time*: `{up_time}` seconds.
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class CreateInvoice(BaseModel):
|
|||||||
internal: bool = False
|
internal: bool = False
|
||||||
out: bool = True
|
out: bool = True
|
||||||
amount: float = Query(None, ge=0)
|
amount: float = Query(None, ge=0)
|
||||||
memo: str | None = None
|
memo: str | None = Query(None, max_length=640)
|
||||||
description_hash: str | None = None
|
description_hash: str | None = None
|
||||||
unhashed_description: str | None = None
|
unhashed_description: str | None = None
|
||||||
expiry: int | None = None
|
expiry: int | None = None
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ async def create_invoice(
|
|||||||
if not user_wallet:
|
if not user_wallet:
|
||||||
raise InvoiceError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
raise InvoiceError(f"Could not fetch wallet '{wallet_id}'.", status="failed")
|
||||||
|
|
||||||
invoice_memo = None if description_hash else memo
|
invoice_memo = None if description_hash else memo[:640]
|
||||||
|
|
||||||
# use the fake wallet if the invoice is for internal use only
|
# use the fake wallet if the invoice is for internal use only
|
||||||
funding_source = fake_wallet if internal else get_funding_source()
|
funding_source = fake_wallet if internal else get_funding_source()
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ async def create_user_account(
|
|||||||
|
|
||||||
|
|
||||||
async def create_user_account_no_ckeck(
|
async def create_user_account_no_ckeck(
|
||||||
account: Optional[Account] = None, wallet_name: Optional[str] = None
|
account: Optional[Account] = None,
|
||||||
|
wallet_name: Optional[str] = None,
|
||||||
|
default_exts: Optional[list[str]] = None,
|
||||||
) -> User:
|
) -> User:
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
@@ -69,9 +71,13 @@ async def create_user_account_no_ckeck(
|
|||||||
wallet_name=wallet_name or settings.lnbits_default_wallet_name,
|
wallet_name=wallet_name or settings.lnbits_default_wallet_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
for ext_id in settings.lnbits_user_default_extensions:
|
user_extensions = (default_exts or []) + settings.lnbits_user_default_extensions
|
||||||
user_ext = UserExtension(user=account.id, extension=ext_id, active=True)
|
for ext_id in user_extensions:
|
||||||
await update_user_extension(user_ext)
|
try:
|
||||||
|
user_ext = UserExtension(user=account.id, extension=ext_id, active=True)
|
||||||
|
await create_user_extension(user_ext)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error enabeling default extension {ext_id}: {e}")
|
||||||
|
|
||||||
user = await get_user_from_account(account)
|
user = await get_user_from_account(account)
|
||||||
assert user, "Cannot find user for account."
|
assert user, "Cannot find user for account."
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<q-tab-panel name="library">
|
||||||
|
<q-card-section class="q-pa-none">
|
||||||
|
<h6 class="q-my-none q-mb-sm">
|
||||||
|
<span v-text="$t('image_library')"></span>
|
||||||
|
</h6>
|
||||||
|
<q-btn
|
||||||
|
color="primary"
|
||||||
|
label="Add Image"
|
||||||
|
@click="$refs.imageInput.click()"
|
||||||
|
class="q-mb-md"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref="imageInput"
|
||||||
|
accept="image/png, image/jpeg, image/gif"
|
||||||
|
style="display: none"
|
||||||
|
@change="onImageInput"
|
||||||
|
/>
|
||||||
|
</q-card-section>
|
||||||
|
<div class="row q-col-gutter-sm q-pa-sm">
|
||||||
|
<div
|
||||||
|
v-for="image in library_images"
|
||||||
|
:key="image.filename"
|
||||||
|
class="col-6 col-sm-4 col-md-3 col-lg-2"
|
||||||
|
style="max-width: 200px"
|
||||||
|
>
|
||||||
|
<q-card class="q-mb-sm">
|
||||||
|
<q-img :src="image.url" style="height: 150px" />
|
||||||
|
|
||||||
|
<q-card-section
|
||||||
|
class="q-pt-md q-pb-md row items-center justify-between"
|
||||||
|
>
|
||||||
|
<small
|
||||||
|
><div
|
||||||
|
class="text-caption ellipsis"
|
||||||
|
style="max-width: 100px"
|
||||||
|
:title="image.filename"
|
||||||
|
v-text="image.filename"
|
||||||
|
></div
|
||||||
|
></small>
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
size="sm"
|
||||||
|
icon="content_copy"
|
||||||
|
@click="copyText(image.url)"
|
||||||
|
:title="$t('copy')"
|
||||||
|
><q-tooltip>Copy image link</q-tooltip></q-btn
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
dense
|
||||||
|
flat
|
||||||
|
size="sm"
|
||||||
|
icon="delete"
|
||||||
|
color="negative"
|
||||||
|
@click="deleteImage(image.filename)"
|
||||||
|
:title="$t('delete')"
|
||||||
|
><q-tooltip>Delete image</q-tooltip></q-btn
|
||||||
|
>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="library_images.length === 0" class="q-pa-xl">
|
||||||
|
<div class="text-subtitle2 text-grey">No images uploaded yet.</div>
|
||||||
|
</div>
|
||||||
|
</q-tab-panel>
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
filled
|
filled
|
||||||
type="text"
|
type="text"
|
||||||
v-model="formData.lnbits_site_title"
|
v-model="formData.lnbits_site_title"
|
||||||
:label="$t('ui_site_title')"
|
:label="$t('ui_site_title') + $t('ui_changing_remove_lnbits_elements')"
|
||||||
></q-input>
|
></q-input>
|
||||||
<br />
|
<br />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -149,6 +149,14 @@ import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
|||||||
><q-tooltip v-if="!$q.screen.gt.sm"
|
><q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
><span v-text="$t('audit')"></span></q-tooltip
|
><span v-text="$t('audit')"></span></q-tooltip
|
||||||
></q-tab>
|
></q-tab>
|
||||||
|
<q-tab
|
||||||
|
name="library"
|
||||||
|
icon="image"
|
||||||
|
:label="$q.screen.gt.sm ? $t('library') : null"
|
||||||
|
@update="val => tab = val.name"
|
||||||
|
><q-tooltip v-if="!$q.screen.gt.sm"
|
||||||
|
><span v-text="$t('library')"></span></q-tooltip
|
||||||
|
></q-tab>
|
||||||
<q-tab
|
<q-tab
|
||||||
style="word-break: break-all"
|
style="word-break: break-all"
|
||||||
name="site_customisation"
|
name="site_customisation"
|
||||||
@@ -177,7 +185,8 @@ import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
|||||||
"admin/_tab_extensions.html" %} {% include
|
"admin/_tab_extensions.html" %} {% include
|
||||||
"admin/_tab_notifications.html" %} {% include
|
"admin/_tab_notifications.html" %} {% include
|
||||||
"admin/_tab_security.html" %} {% include "admin/_tab_theme.html"
|
"admin/_tab_security.html" %} {% include "admin/_tab_theme.html"
|
||||||
%}{% include "admin/_tab_audit.html"%}
|
%}{% include "admin/_tab_audit.html"%}{% include
|
||||||
|
"admin/_tab_library.html"%}
|
||||||
</q-tab-panels>
|
</q-tab-panels>
|
||||||
</q-form>
|
</q-form>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -85,7 +85,12 @@
|
|||||||
<q-card-section>
|
<q-card-section>
|
||||||
<code><span class="text-light-green">GET</span> /api/v1/wallet</code>
|
<code><span class="text-light-green">GET</span> /api/v1/wallet</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||||
<code>{"X-Api-Key": "<i v-text="wallet.inkey"></i>"}</code><br />
|
<code
|
||||||
|
>{"X-Api-Key": "<i
|
||||||
|
v-text="inkeyHidden ? '****************' : wallet.inkey"
|
||||||
|
></i
|
||||||
|
>"}</code
|
||||||
|
><br />
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 200 OK (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
@@ -96,7 +101,8 @@
|
|||||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||||
<code
|
<code
|
||||||
>curl <span v-text="baseUrl"></span>api/v1/wallet -H "X-Api-Key:
|
>curl <span v-text="baseUrl"></span>api/v1/wallet -H "X-Api-Key:
|
||||||
<i v-text="wallet.inkey"></i>"</code
|
<i v-text="inkeyHidden ? '****************' : wallet.inkey"></i
|
||||||
|
>"</code
|
||||||
>
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
@@ -112,7 +118,12 @@
|
|||||||
<q-card-section>
|
<q-card-section>
|
||||||
<code><span class="text-light-green">POST</span> /api/v1/payments</code>
|
<code><span class="text-light-green">POST</span> /api/v1/payments</code>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||||
<code>{"X-Api-Key": "<i v-text="wallet.inkey"></i>"}</code><br />
|
<code
|
||||||
|
>{"X-Api-Key": "<i
|
||||||
|
v-text="inkeyHidden ? '****************' : wallet.inkey"
|
||||||
|
></i
|
||||||
|
>"}</code
|
||||||
|
><br />
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||||
<code
|
<code
|
||||||
>{"out": false, "amount": <int>, "memo": <string>,
|
>{"out": false, "amount": <int>, "memo": <string>,
|
||||||
@@ -130,8 +141,9 @@
|
|||||||
<code
|
<code
|
||||||
>curl -X POST <span v-text="baseUrl"></span>api/v1/payments -d
|
>curl -X POST <span v-text="baseUrl"></span>api/v1/payments -d
|
||||||
'{"out": false, "amount": <int>, "memo": <string>}' -H
|
'{"out": false, "amount": <int>, "memo": <string>}' -H
|
||||||
"X-Api-Key: <i v-text="wallet.inkey"></i>" -H "Content-type:
|
"X-Api-Key:
|
||||||
application/json"</code
|
<i v-text="inkeyHidden ? '****************' : wallet.inkey"></i>" -H
|
||||||
|
"Content-type: application/json"</code
|
||||||
>
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
@@ -218,7 +230,12 @@
|
|||||||
/api/v1/payments/<payment_hash></code
|
/api/v1/payments/<payment_hash></code
|
||||||
>
|
>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||||
<code>{"X-Api-Key": "{{ wallet.inkey }}"}</code>
|
<code
|
||||||
|
>{"X-Api-Key": "<i
|
||||||
|
v-text="inkeyHidden ? '****************' : wallet.inkey"
|
||||||
|
></i
|
||||||
|
>"}</code
|
||||||
|
>
|
||||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
Returns 200 OK (application/json)
|
Returns 200 OK (application/json)
|
||||||
</h5>
|
</h5>
|
||||||
@@ -227,11 +244,33 @@
|
|||||||
<code
|
<code
|
||||||
>curl -X GET
|
>curl -X GET
|
||||||
<span v-text="baseUrl"></span>api/v1/payments/<payment_hash> -H
|
<span v-text="baseUrl"></span>api/v1/payments/<payment_hash> -H
|
||||||
"X-Api-Key: <i v-text="wallet.inkey"></i>" -H "Content-type:
|
"X-Api-Key:
|
||||||
application/json"</code
|
<i v-text="inkeyHidden ? '****************' : wallet.inkey"></i>" -H
|
||||||
|
"Content-type: application/json"</code
|
||||||
>
|
>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<code
|
||||||
|
><span class="text-pink">WS</span>
|
||||||
|
/api/v1/ws/<invoice_key></code
|
||||||
|
>
|
||||||
|
<h5
|
||||||
|
class="text-caption q-mt-sm q-mb-none"
|
||||||
|
v-text="$t('websocket_example')"
|
||||||
|
></h5>
|
||||||
|
<code
|
||||||
|
>wscat -c <span v-text="websocketUrl"></span>/<span
|
||||||
|
v-text="inkeyHidden ? '****************' : wallet.inkey"
|
||||||
|
></span
|
||||||
|
></code>
|
||||||
|
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||||
|
Returns 200 OK (application/json)/payments
|
||||||
|
</h5>
|
||||||
|
<code>{"balance": <int>, "payment": <object>}</code>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
:type="loginData.isPwdRepeat ? 'password' : 'text'"
|
:type="loginData.isPwdRepeat ? 'password' : 'text'"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
:label="$t('password_repeat')"
|
:label="$t('password_repeat')"
|
||||||
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password'), (val) => val === loginData.password || 'Passwords_dont_match']"
|
:rules="[(val) => !val || val.length >= 8 || $t('invalid_password'), (val) => val === loginData.password || $t('invalid_password_repeat')]"
|
||||||
><template v-slot:append>
|
><template v-slot:append>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="loginData.isPwdRepeat ? 'visibility_off' : 'visibility'"
|
:name="loginData.isPwdRepeat ? 'visibility_off' : 'visibility'"
|
||||||
|
|||||||
@@ -184,12 +184,13 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p v-else-if="authAction === 'register'" class="q-mb-none">
|
<p v-else-if="authAction === 'register'" class="q-mb-none">
|
||||||
Aready have an account?
|
<span v-text="$t('existing_account_question')"></span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="text-secondary cursor-pointer"
|
class="text-secondary cursor-pointer"
|
||||||
@click="showLogin('username-password')"
|
@click="showLogin('username-password')"
|
||||||
>Login</span
|
v-text="$t('login')"
|
||||||
>
|
></span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</username-password>
|
</username-password>
|
||||||
@@ -225,7 +226,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="'{{ LNBITS_DENOMINATION }}' == 'sats'"
|
v-if="'{{ LNBITS_DENOMINATION }}' == 'sats' && '{{ SITE_TITLE }}' == 'LNbits' && '{{ LNBITS_SHOW_HOME_PAGE_ELEMENTS }}' == 'True'"
|
||||||
class="full-width q-mb-lg q-mt-sm"
|
class="full-width q-mb-lg q-mt-sm"
|
||||||
>
|
>
|
||||||
<div class="flex flex-center q-gutter-md q-py-md">
|
<div class="flex flex-center q-gutter-md q-py-md">
|
||||||
@@ -281,7 +282,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div v-if="'{{ LNBITS_DENOMINATION }}' == 'sats'" class="full-width">
|
<div
|
||||||
|
v-if="'{{ LNBITS_DENOMINATION }}' == 'sats' && '{{ SITE_TITLE }}' == 'LNbits' && '{{ LNBITS_SHOW_HOME_PAGE_ELEMENTS }}' == 'True'"
|
||||||
|
class="full-width"
|
||||||
|
>
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="marquee">
|
<div class="marquee">
|
||||||
<div class="marquee__group">
|
<div class="marquee__group">
|
||||||
|
|||||||
@@ -59,9 +59,11 @@
|
|||||||
<div class="col-7">
|
<div class="col-7">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<div class="text-h3 q-my-none text-no-wrap">
|
<div class="text-h3 q-my-none full-width">
|
||||||
<strong
|
<strong
|
||||||
v-text="walletFormatBalance(this.g.wallet.sat)"
|
v-text="walletFormatBalance(this.g.wallet.sat)"
|
||||||
|
class="text-no-wrap"
|
||||||
|
:style="{fontSize: 'clamp(0.75rem, 10vw, 3rem)', display: 'inline-block', maxWidth: '100%'}"
|
||||||
></strong>
|
></strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -166,7 +168,12 @@
|
|||||||
@click="showReceiveDialog"
|
@click="showReceiveDialog"
|
||||||
:label="$t('create_invoice')"
|
:label="$t('create_invoice')"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-btn unelevated color="secondary" icon="qr_code_scanner">
|
<q-btn
|
||||||
|
unelevated
|
||||||
|
color="secondary"
|
||||||
|
icon="qr_code_scanner"
|
||||||
|
@click="showCamera"
|
||||||
|
>
|
||||||
<q-tooltip
|
<q-tooltip
|
||||||
><span v-text="$t('camera_tooltip')"></span
|
><span v-text="$t('camera_tooltip')"></span
|
||||||
></q-tooltip>
|
></q-tooltip>
|
||||||
|
|||||||
@@ -127,7 +127,17 @@
|
|||||||
:label="props.row.wallet_count"
|
:label="props.row.wallet_count"
|
||||||
@click="fetchWallets(props.row.id)"
|
@click="fetchWallets(props.row.id)"
|
||||||
>
|
>
|
||||||
<q-tooltip>Show Wallets</q-tooltip>
|
</q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="(users.length == 1) && searchData.wallet_id"
|
||||||
|
round
|
||||||
|
icon="menu"
|
||||||
|
size="sm"
|
||||||
|
color="secondary"
|
||||||
|
class="q-ml-sm"
|
||||||
|
@click="showWalletPayments(searchData.wallet_id)"
|
||||||
|
>
|
||||||
|
<q-tooltip>Show Payments</q-tooltip>
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</q-td>
|
</q-td>
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
from shutil import make_archive
|
from pathlib import Path
|
||||||
|
from shutil import make_archive, move
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
from typing import Optional
|
from tempfile import NamedTemporaryFile
|
||||||
|
from typing import IO, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
import filetype
|
||||||
|
from fastapi import APIRouter, Depends, File, Header, HTTPException, UploadFile
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
|
||||||
from lnbits.core.models import User
|
from lnbits.core.models import User
|
||||||
|
from lnbits.core.models.misc import Image, SimpleStatus
|
||||||
from lnbits.core.models.notifications import NotificationType
|
from lnbits.core.models.notifications import NotificationType
|
||||||
from lnbits.core.services import (
|
from lnbits.core.services import (
|
||||||
enqueue_notification,
|
enqueue_notification,
|
||||||
@@ -19,6 +23,7 @@ from lnbits.core.services import (
|
|||||||
from lnbits.core.services.notifications import send_email_notification
|
from lnbits.core.services.notifications import send_email_notification
|
||||||
from lnbits.core.services.settings import dict_to_settings
|
from lnbits.core.services.settings import dict_to_settings
|
||||||
from lnbits.decorators import check_admin, check_super_user
|
from lnbits.decorators import check_admin, check_super_user
|
||||||
|
from lnbits.helpers import safe_upload_file_path
|
||||||
from lnbits.server import server_restart
|
from lnbits.server import server_restart
|
||||||
from lnbits.settings import AdminSettings, Settings, UpdateSettings, settings
|
from lnbits.settings import AdminSettings, Settings, UpdateSettings, settings
|
||||||
from lnbits.tasks import invoice_listeners
|
from lnbits.tasks import invoice_listeners
|
||||||
@@ -27,6 +32,7 @@ from .. import core_app_extra
|
|||||||
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
|
from ..crud import delete_admin_settings, get_admin_settings, update_admin_settings
|
||||||
|
|
||||||
admin_router = APIRouter(tags=["Admin UI"], prefix="/admin")
|
admin_router = APIRouter(tags=["Admin UI"], prefix="/admin")
|
||||||
|
file_upload = File(...)
|
||||||
|
|
||||||
|
|
||||||
@admin_router.get(
|
@admin_router.get(
|
||||||
@@ -159,3 +165,93 @@ async def api_download_backup() -> FileResponse:
|
|||||||
return FileResponse(
|
return FileResponse(
|
||||||
path=f"{last_filename}.zip", filename=filename, media_type="application/zip"
|
path=f"{last_filename}.zip", filename=filename, media_type="application/zip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.post(
|
||||||
|
"/api/v1/images",
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
dependencies=[Depends(check_admin)],
|
||||||
|
)
|
||||||
|
async def upload_image(
|
||||||
|
file: UploadFile = file_upload,
|
||||||
|
content_length: int = Header(..., le=settings.lnbits_upload_size_bytes),
|
||||||
|
) -> Image:
|
||||||
|
if not file.filename:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST, detail="No filename provided."
|
||||||
|
)
|
||||||
|
|
||||||
|
# validate file types
|
||||||
|
file_info = filetype.guess(file.file)
|
||||||
|
if file_info is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
|
||||||
|
detail="Unable to determine file type",
|
||||||
|
)
|
||||||
|
detected_content_type = file_info.extension.lower()
|
||||||
|
if (
|
||||||
|
file.content_type not in settings.lnbits_upload_allowed_types
|
||||||
|
or detected_content_type not in settings.lnbits_upload_allowed_types
|
||||||
|
):
|
||||||
|
raise HTTPException(HTTPStatus.UNSUPPORTED_MEDIA_TYPE, "Unsupported file type")
|
||||||
|
|
||||||
|
# validate file name
|
||||||
|
try:
|
||||||
|
file_path = safe_upload_file_path(file.filename)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail=f"The requested filename '{file.filename}' is forbidden.",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
# validate file size
|
||||||
|
real_file_size = 0
|
||||||
|
temp: IO = NamedTemporaryFile(delete=False)
|
||||||
|
for chunk in file.file:
|
||||||
|
real_file_size += len(chunk)
|
||||||
|
if real_file_size > content_length:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.REQUEST_ENTITY_TOO_LARGE,
|
||||||
|
detail=f"File too large ({content_length / 1000} KB max)",
|
||||||
|
)
|
||||||
|
temp.write(chunk)
|
||||||
|
temp.close()
|
||||||
|
|
||||||
|
move(temp.name, file_path)
|
||||||
|
return Image(filename=file.filename)
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.get(
|
||||||
|
"/api/v1/images",
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
dependencies=[Depends(check_admin)],
|
||||||
|
)
|
||||||
|
async def list_uploaded_images() -> list[Image]:
|
||||||
|
image_folder = Path(settings.lnbits_data_folder, "images")
|
||||||
|
files = image_folder.glob("*")
|
||||||
|
images = []
|
||||||
|
for file in files:
|
||||||
|
if file.is_file():
|
||||||
|
images.append(Image(filename=file.name))
|
||||||
|
return images
|
||||||
|
|
||||||
|
|
||||||
|
@admin_router.delete(
|
||||||
|
"/api/v1/images/{filename}",
|
||||||
|
status_code=HTTPStatus.OK,
|
||||||
|
dependencies=[Depends(check_admin)],
|
||||||
|
)
|
||||||
|
async def delete_uploaded_image(filename: str) -> SimpleStatus:
|
||||||
|
try:
|
||||||
|
file_path = safe_upload_file_path(filename)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.FORBIDDEN,
|
||||||
|
detail=f"The requested filename '{filename}' is forbidden.",
|
||||||
|
) from e
|
||||||
|
|
||||||
|
if not file_path.exists():
|
||||||
|
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Image not found.")
|
||||||
|
|
||||||
|
file_path.unlink()
|
||||||
|
return SimpleStatus(success=True, message=f"{filename} deleted")
|
||||||
|
|||||||
@@ -104,16 +104,24 @@ async def api_create_user(data: CreateUser) -> CreateUser:
|
|||||||
)
|
)
|
||||||
account.validate_fields()
|
account.validate_fields()
|
||||||
account.hash_password(data.password)
|
account.hash_password(data.password)
|
||||||
user = await create_user_account_no_ckeck(account)
|
user = await create_user_account_no_ckeck(account, default_exts=data.extensions)
|
||||||
data.id = user.id
|
data.id = user.id
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@users_router.put("/user/{user_id}", name="Update user")
|
@users_router.put("/user/{user_id}", name="Update user")
|
||||||
async def api_update_user(user_id: str, data: CreateUser) -> CreateUser:
|
async def api_update_user(
|
||||||
|
user_id: str, data: CreateUser, user: User = Depends(check_admin)
|
||||||
|
) -> CreateUser:
|
||||||
if user_id != data.id:
|
if user_id != data.id:
|
||||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "User Id missmatch.")
|
raise HTTPException(HTTPStatus.BAD_REQUEST, "User Id missmatch.")
|
||||||
|
|
||||||
|
if user_id == settings.super_user and user.id != settings.super_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Action only allowed for super user.",
|
||||||
|
)
|
||||||
|
|
||||||
if data.password or data.password_repeat:
|
if data.password or data.password_repeat:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
HTTPStatus.BAD_REQUEST, "Use 'reset password' functionality."
|
HTTPStatus.BAD_REQUEST, "Use 'reset password' functionality."
|
||||||
@@ -255,13 +263,22 @@ async def api_users_undelete_user_wallet(user_id: str, wallet: str) -> SimpleSta
|
|||||||
summary="First time it is called it does a soft delete (only sets a flag)."
|
summary="First time it is called it does a soft delete (only sets a flag)."
|
||||||
"The second time it is called will delete the entry from the DB",
|
"The second time it is called will delete the entry from the DB",
|
||||||
)
|
)
|
||||||
async def api_users_delete_user_wallet(user_id: str, wallet: str) -> SimpleStatus:
|
async def api_users_delete_user_wallet(
|
||||||
|
user_id: str, wallet: str, user: User = Depends(check_admin)
|
||||||
|
) -> SimpleStatus:
|
||||||
wal = await get_wallet(wallet)
|
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,
|
||||||
detail="Wallet does not exist.",
|
detail="Wallet does not exist.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if user_id == settings.super_user and user.id != settings.super_user:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=HTTPStatus.BAD_REQUEST,
|
||||||
|
detail="Action only allowed for super user.",
|
||||||
|
)
|
||||||
|
|
||||||
if wal.deleted:
|
if wal.deleted:
|
||||||
await force_delete_wallet(wallet)
|
await force_delete_wallet(wallet)
|
||||||
await delete_wallet(user_id=user_id, wallet_id=wallet)
|
await delete_wallet(user_id=user_id, wallet_id=wallet)
|
||||||
|
|||||||
+12
-4
@@ -5,7 +5,7 @@ import jwt
|
|||||||
from fastapi import Cookie, Depends, Query, Request, Security
|
from fastapi import Cookie, Depends, Query, Request, Security
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.openapi.models import APIKey, APIKeyIn, SecuritySchemeType
|
from fastapi.openapi.models import APIKey, APIKeyIn, SecuritySchemeType
|
||||||
from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer
|
from fastapi.security import APIKeyHeader, APIKeyQuery, HTTPBearer, OAuth2PasswordBearer
|
||||||
from fastapi.security.base import SecurityBase
|
from fastapi.security.base import SecurityBase
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from pydantic.types import UUID4
|
from pydantic.types import UUID4
|
||||||
@@ -31,8 +31,15 @@ from lnbits.db import Connection, Filter, Filters, TFilterModel
|
|||||||
from lnbits.helpers import path_segments
|
from lnbits.helpers import path_segments
|
||||||
from lnbits.settings import AuthMethods, settings
|
from lnbits.settings import AuthMethods, settings
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/auth", auto_error=False)
|
oauth2_scheme = OAuth2PasswordBearer(
|
||||||
|
tokenUrl="api/v1/auth",
|
||||||
|
auto_error=False,
|
||||||
|
description="OAuth2 access token for authentication with username and password.",
|
||||||
|
)
|
||||||
|
http_bearer = HTTPBearer(
|
||||||
|
auto_error=False,
|
||||||
|
description="Bearer Token for custom ACL based access control",
|
||||||
|
)
|
||||||
api_key_header = APIKeyHeader(
|
api_key_header = APIKeyHeader(
|
||||||
name="X-API-KEY",
|
name="X-API-KEY",
|
||||||
auto_error=False,
|
auto_error=False,
|
||||||
@@ -132,8 +139,9 @@ async def require_invoice_key(
|
|||||||
async def check_access_token(
|
async def check_access_token(
|
||||||
header_access_token: Annotated[Union[str, None], Depends(oauth2_scheme)],
|
header_access_token: Annotated[Union[str, None], Depends(oauth2_scheme)],
|
||||||
cookie_access_token: Annotated[Union[str, None], Cookie()] = None,
|
cookie_access_token: Annotated[Union[str, None], Cookie()] = None,
|
||||||
|
bearer_access_token: Annotated[Union[str, None], Depends(http_bearer)] = None,
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
return header_access_token or cookie_access_token
|
return header_access_token or cookie_access_token or bearer_access_token
|
||||||
|
|
||||||
|
|
||||||
async def check_user_exists(
|
async def check_user_exists(
|
||||||
|
|||||||
+16
-1
@@ -71,7 +71,11 @@ def template_renderer(additional_folders: Optional[list] = None) -> Jinja2Templa
|
|||||||
t.env.globals["VOIDWALLET"] = settings.lnbits_backend_wallet_class == "VoidWallet"
|
t.env.globals["VOIDWALLET"] = settings.lnbits_backend_wallet_class == "VoidWallet"
|
||||||
t.env.globals["HIDE_API"] = settings.lnbits_hide_api
|
t.env.globals["HIDE_API"] = settings.lnbits_hide_api
|
||||||
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
||||||
t.env.globals["LNBITS_DENOMINATION"] = settings.lnbits_denomination
|
t.env.globals["LNBITS_DENOMINATION"] = (
|
||||||
|
settings.lnbits_denomination
|
||||||
|
if settings.lnbits_backend_wallet_class == "FakeWallet"
|
||||||
|
else "sats"
|
||||||
|
)
|
||||||
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
|
t.env.globals["SITE_TAGLINE"] = settings.lnbits_site_tagline
|
||||||
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
|
t.env.globals["SITE_DESCRIPTION"] = settings.lnbits_site_description
|
||||||
t.env.globals["LNBITS_SHOW_HOME_PAGE_ELEMENTS"] = (
|
t.env.globals["LNBITS_SHOW_HOME_PAGE_ELEMENTS"] = (
|
||||||
@@ -343,3 +347,14 @@ def path_segments(path: str) -> list[str]:
|
|||||||
def normalize_path(path: Optional[str]) -> str:
|
def normalize_path(path: Optional[str]) -> str:
|
||||||
path = path or ""
|
path = path or ""
|
||||||
return "/" + "/".join(path_segments(path))
|
return "/" + "/".join(path_segments(path))
|
||||||
|
|
||||||
|
|
||||||
|
def safe_upload_file_path(filename: str, directory: str = "images") -> Path:
|
||||||
|
image_folder = Path(settings.lnbits_data_folder, directory)
|
||||||
|
file_path = image_folder / filename
|
||||||
|
# Prevent dir traversal attack
|
||||||
|
if image_folder.resolve() not in file_path.resolve().parents:
|
||||||
|
raise ValueError("Unsafe filename.")
|
||||||
|
# Prevent filename with subdirectories
|
||||||
|
file_path = image_folder / filename.split("/")[-1]
|
||||||
|
return file_path.resolve()
|
||||||
|
|||||||
+18
-1
@@ -260,7 +260,7 @@ class ThemesSettings(LNbitsSettings):
|
|||||||
lnbits_ad_space_enabled: bool = Field(default=False)
|
lnbits_ad_space_enabled: bool = Field(default=False)
|
||||||
lnbits_allowed_currencies: list[str] = Field(default=[])
|
lnbits_allowed_currencies: list[str] = Field(default=[])
|
||||||
lnbits_default_accounting_currency: str | None = Field(default=None)
|
lnbits_default_accounting_currency: str | None = Field(default=None)
|
||||||
lnbits_qr_logo: str = Field(default="/static/favicon.ico")
|
lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png")
|
||||||
lnbits_default_reaction: str = Field(default="confettiBothSides")
|
lnbits_default_reaction: str = Field(default="confettiBothSides")
|
||||||
lnbits_default_theme: str = Field(default="salvador")
|
lnbits_default_theme: str = Field(default="salvador")
|
||||||
lnbits_default_border: str = Field(default="hard-border")
|
lnbits_default_border: str = Field(default="hard-border")
|
||||||
@@ -271,6 +271,23 @@ class ThemesSettings(LNbitsSettings):
|
|||||||
class OpsSettings(LNbitsSettings):
|
class OpsSettings(LNbitsSettings):
|
||||||
lnbits_baseurl: str = Field(default="http://127.0.0.1:5000/")
|
lnbits_baseurl: str = Field(default="http://127.0.0.1:5000/")
|
||||||
lnbits_hide_api: bool = Field(default=False)
|
lnbits_hide_api: bool = Field(default=False)
|
||||||
|
lnbits_upload_size_bytes: int = Field(default=512_000, ge=0) # 500kb
|
||||||
|
lnbits_upload_allowed_types: list[str] = Field(
|
||||||
|
default=[
|
||||||
|
"image/png",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
"image/heic",
|
||||||
|
"image/heif",
|
||||||
|
"image/heics",
|
||||||
|
"png",
|
||||||
|
"jpeg",
|
||||||
|
"jpg",
|
||||||
|
"heic",
|
||||||
|
"heif",
|
||||||
|
"heics",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FeeSettings(LNbitsSettings):
|
class FeeSettings(LNbitsSettings):
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
@@ -27,6 +27,7 @@ window.localisation.en = {
|
|||||||
close_channel: 'Close Channel',
|
close_channel: 'Close Channel',
|
||||||
close: 'Close',
|
close: 'Close',
|
||||||
restart: 'Restart server',
|
restart: 'Restart server',
|
||||||
|
image_library: 'Image Library',
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
save_tooltip: 'Save your changes',
|
save_tooltip: 'Save your changes',
|
||||||
credit_debit: 'Credit / Debit',
|
credit_debit: 'Credit / Debit',
|
||||||
@@ -64,7 +65,6 @@ window.localisation.en = {
|
|||||||
donate: 'Donate',
|
donate: 'Donate',
|
||||||
view_github: 'View on GitHub',
|
view_github: 'View on GitHub',
|
||||||
voidwallet_active: 'VoidWallet is active! Payments disabled',
|
voidwallet_active: 'VoidWallet is active! Payments disabled',
|
||||||
use_with_caution: 'USE WITH CAUTION - {name} wallet is still in BETA',
|
|
||||||
service_fee_badge: 'Service fee: {amount} % per transaction',
|
service_fee_badge: 'Service fee: {amount} % per transaction',
|
||||||
service_fee_max_badge:
|
service_fee_max_badge:
|
||||||
'Service fee: {amount} % per transaction (max {max} {denom})',
|
'Service fee: {amount} % per transaction (max {max} {denom})',
|
||||||
@@ -117,7 +117,7 @@ window.localisation.en = {
|
|||||||
copy_wallet_url: 'Copy wallet URL',
|
copy_wallet_url: 'Copy wallet URL',
|
||||||
disclaimer_dialog_title: 'Important!',
|
disclaimer_dialog_title: 'Important!',
|
||||||
disclaimer_dialog:
|
disclaimer_dialog:
|
||||||
'You *must* save your login credentials to be able to access your wallet again. If you lose them, you will lose access to your wallet and funds.\n\nFind your login credentials on your account settings page.\n\nThis service is in BETA. LNbits holds no responsibility for loss of access to funds.',
|
'You *must* save your login credentials to be able to access your wallet again. If you lose them, you will lose access to your wallet and funds.\n\nFind your login credentials on your account settings page.\n\nLNbits holds no responsibility for loss of access to funds.',
|
||||||
no_transactions: 'No transactions made yet',
|
no_transactions: 'No transactions made yet',
|
||||||
manage: 'Manage',
|
manage: 'Manage',
|
||||||
exchanges: 'Exchanges',
|
exchanges: 'Exchanges',
|
||||||
@@ -200,7 +200,7 @@ window.localisation.en = {
|
|||||||
|
|
||||||
notifications_email_config: 'Email Configuration',
|
notifications_email_config: 'Email Configuration',
|
||||||
notifications_enable_email: 'Enable Email',
|
notifications_enable_email: 'Enable Email',
|
||||||
notifications_enable_email_desc: 'Send notfications over email',
|
notifications_enable_email_desc: 'Send notifications over email',
|
||||||
notifications_send_test_email: 'Send test email',
|
notifications_send_test_email: 'Send test email',
|
||||||
notifications_send_email: 'Send email',
|
notifications_send_email: 'Send email',
|
||||||
notifications_send_email_desc: 'Email you will send from',
|
notifications_send_email_desc: 'Email you will send from',
|
||||||
@@ -322,6 +322,7 @@ window.localisation.en = {
|
|||||||
update_pubkey: 'Update Public Key',
|
update_pubkey: 'Update Public Key',
|
||||||
set_password: 'Set Password',
|
set_password: 'Set Password',
|
||||||
invalid_password: 'Password must have at least 8 characters',
|
invalid_password: 'Password must have at least 8 characters',
|
||||||
|
invalid_password_repeat: 'Passwords do not match',
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
username: 'Username',
|
username: 'Username',
|
||||||
@@ -337,6 +338,7 @@ window.localisation.en = {
|
|||||||
invalid_username: 'Invalid Username',
|
invalid_username: 'Invalid Username',
|
||||||
auth_provider: 'Auth Provider',
|
auth_provider: 'Auth Provider',
|
||||||
my_account: 'My Account',
|
my_account: 'My Account',
|
||||||
|
existing_account_question: 'Already have an account?',
|
||||||
background_image: 'Background Image',
|
background_image: 'Background Image',
|
||||||
back: 'Back',
|
back: 'Back',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
@@ -495,9 +497,11 @@ window.localisation.en = {
|
|||||||
'Disable Service Fee for Internal Lightning Payments',
|
'Disable Service Fee for Internal Lightning Payments',
|
||||||
ui_management: 'UI Management',
|
ui_management: 'UI Management',
|
||||||
ui_site_title: 'Site Title',
|
ui_site_title: 'Site Title',
|
||||||
|
ui_changing_remove_lnbits_elements:
|
||||||
|
' (changing will remove LNbits elements on the homepage and footer)',
|
||||||
ui_site_tagline: 'Site Tagline',
|
ui_site_tagline: 'Site Tagline',
|
||||||
ui_elements_enable: 'Enable elements on homepage',
|
ui_elements_enable: 'Enable elements on homepage/footer',
|
||||||
ui_elements_disable: 'Disable elements on homepage',
|
ui_elements_disable: 'Disable elements on homepage/footer',
|
||||||
ui_toggle_elements_tip: "Remove homepage elements like 'runs on' etc",
|
ui_toggle_elements_tip: "Remove homepage elements like 'runs on' etc",
|
||||||
ui_site_description: 'Site Description',
|
ui_site_description: 'Site Description',
|
||||||
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
|
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
|
||||||
@@ -513,8 +517,7 @@ window.localisation.en = {
|
|||||||
ui_custom_image_label: 'URL to custom image',
|
ui_custom_image_label: 'URL to custom image',
|
||||||
ui_custom_image_hint: 'Image showed at homepage/login',
|
ui_custom_image_hint: 'Image showed at homepage/login',
|
||||||
ui_custom_badge: 'Custom Badge',
|
ui_custom_badge: 'Custom Badge',
|
||||||
ui_custom_badge_label:
|
ui_custom_badge_label: "Custom Badge 'USE WITH CAUTION'",
|
||||||
"Custom Badge 'USE WITH CAUTION - LNbits wallet is still in BETA'",
|
|
||||||
ui_custom_badge_color_label: 'Custom Badge Color',
|
ui_custom_badge_color_label: 'Custom Badge Color',
|
||||||
themes: 'Themes',
|
themes: 'Themes',
|
||||||
themes_hint: 'Choose themes available for users',
|
themes_hint: 'Choose themes available for users',
|
||||||
@@ -567,5 +570,6 @@ window.localisation.en = {
|
|||||||
view_list: 'View wallets as list',
|
view_list: 'View wallets as list',
|
||||||
view_column: 'View wallets as rows',
|
view_column: 'View wallets as rows',
|
||||||
filter_payments: 'Filter payments',
|
filter_payments: 'Filter payments',
|
||||||
filter_date: 'Filter by date'
|
filter_date: 'Filter by date',
|
||||||
|
websocket_example: 'Websocket example'
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-55
@@ -5,8 +5,8 @@ window.localisation.fi = {
|
|||||||
site_customisation: 'Sivuston kustomointi',
|
site_customisation: 'Sivuston kustomointi',
|
||||||
funding: 'Rahoitus',
|
funding: 'Rahoitus',
|
||||||
users: 'Käyttäjät',
|
users: 'Käyttäjät',
|
||||||
audit: 'Auditointi',
|
audit: 'Seuranta',
|
||||||
api_watch: 'Api Watch',
|
api_watch: 'API-seuranta',
|
||||||
apps: 'Sovellukset',
|
apps: 'Sovellukset',
|
||||||
channels: 'Kanavat',
|
channels: 'Kanavat',
|
||||||
transactions: 'Tapahtumat',
|
transactions: 'Tapahtumat',
|
||||||
@@ -27,6 +27,7 @@ window.localisation.fi = {
|
|||||||
close_channel: 'Sulje kanava',
|
close_channel: 'Sulje kanava',
|
||||||
close: 'Sulje',
|
close: 'Sulje',
|
||||||
restart: 'Palvelimen uudelleen käynnistys',
|
restart: 'Palvelimen uudelleen käynnistys',
|
||||||
|
image_library: 'Kuvakirjasto',
|
||||||
save: 'Tallenna',
|
save: 'Tallenna',
|
||||||
save_tooltip: 'Tallenna muutokset',
|
save_tooltip: 'Tallenna muutokset',
|
||||||
credit_debit: 'Hyvitä / Veloita',
|
credit_debit: 'Hyvitä / Veloita',
|
||||||
@@ -37,7 +38,7 @@ window.localisation.fi = {
|
|||||||
'Virtuaalivarojen ({amount} sat) hyvitys-/veloitustapahtuma onnistui. Maksukyky riippuuu rahoituslähteen todellisista varoista.',
|
'Virtuaalivarojen ({amount} sat) hyvitys-/veloitustapahtuma onnistui. Maksukyky riippuuu rahoituslähteen todellisista varoista.',
|
||||||
restart_tooltip: 'Uudelleenkäynnistä palvelu muutosten käyttöönottamiseksi',
|
restart_tooltip: 'Uudelleenkäynnistä palvelu muutosten käyttöönottamiseksi',
|
||||||
add_funds_tooltip: 'Lisää varoja lompakkoon',
|
add_funds_tooltip: 'Lisää varoja lompakkoon',
|
||||||
reset_defaults: 'Peruuta muutokset',
|
reset_defaults: 'Palauta oletusasetukset',
|
||||||
reset_defaults_tooltip:
|
reset_defaults_tooltip:
|
||||||
'Poista kaikki asetusten muutokset ja palauta järjestelmän oletusasetukset.',
|
'Poista kaikki asetusten muutokset ja palauta järjestelmän oletusasetukset.',
|
||||||
download_backup: 'Lataa tietokannan varmuuskopio',
|
download_backup: 'Lataa tietokannan varmuuskopio',
|
||||||
@@ -65,8 +66,7 @@ window.localisation.fi = {
|
|||||||
'Näyttää että sinulla on lunastamattomia bitcoin varoja, mutta sinulla ei vielä ole lompakkoa. Lunasta varat allaolevaa nappia painamalla, ja sinulle luodaan lompakko.',
|
'Näyttää että sinulla on lunastamattomia bitcoin varoja, mutta sinulla ei vielä ole lompakkoa. Lunasta varat allaolevaa nappia painamalla, ja sinulle luodaan lompakko.',
|
||||||
donate: 'Lahjoita',
|
donate: 'Lahjoita',
|
||||||
view_github: 'Näytä GitHub:ssa',
|
view_github: 'Näytä GitHub:ssa',
|
||||||
voidwallet_active:
|
voidwallet_active: 'VoidWallet on aktiivinen. Se ei tue maksutapahtumia!',
|
||||||
'Maksutapahtumat ovat poissa käytöstä, koska VoidWallet on aktiivinen!',
|
|
||||||
use_with_caution:
|
use_with_caution:
|
||||||
'KÄYTÄ VAROEN - BETA-ohjelmisto on käytössä palvelussa: {name}',
|
'KÄYTÄ VAROEN - BETA-ohjelmisto on käytössä palvelussa: {name}',
|
||||||
service_fee_tooltip:
|
service_fee_tooltip:
|
||||||
@@ -84,6 +84,7 @@ window.localisation.fi = {
|
|||||||
create_invoice: 'Laskuta',
|
create_invoice: 'Laskuta',
|
||||||
camera_tooltip: 'Kuvaa lasku tai QR-koodi',
|
camera_tooltip: 'Kuvaa lasku tai QR-koodi',
|
||||||
export_csv: 'Vie CSV-tiedostoon',
|
export_csv: 'Vie CSV-tiedostoon',
|
||||||
|
export_csv_details: 'Vie CSV-tiedostoon lisätietoineen',
|
||||||
chart_tooltip: 'Näytä kaaviokuva',
|
chart_tooltip: 'Näytä kaaviokuva',
|
||||||
pending: 'Odottaa',
|
pending: 'Odottaa',
|
||||||
copy_invoice: 'Kopioi lasku',
|
copy_invoice: 'Kopioi lasku',
|
||||||
@@ -113,10 +114,10 @@ window.localisation.fi = {
|
|||||||
copy_wallet_url: 'Kopioi lompakon URL',
|
copy_wallet_url: 'Kopioi lompakon URL',
|
||||||
disclaimer_dialog_title: 'Tärkeää!',
|
disclaimer_dialog_title: 'Tärkeää!',
|
||||||
disclaimer_dialog:
|
disclaimer_dialog:
|
||||||
'Sinun *PITÄÄ TALLETTAA* kirjautumistietosi turvallisesta ja helposti saataville, jotta pääset jatkossa kirjautumaan lompakkoosi! Löydät kirjautumistiedot Tilin asetukset -sivulta. Tämä palvelu on kokeiluvaiheessa (eli BETA), ja niinpä kukaan ei ota mitään vastuuta varojen säilymisestä tai niiden käytettävyyden takaamisesta.',
|
'Sinun *PITÄÄ TALLETTAA* kirjautumistietosi turvallisesta ja helposti saataville, jotta pääset jatkossa kirjautumaan lompakkoosi! Löydät kirjautumistiedot Tilin asetukset -sivulta. Kukaan ei ota mitään vastuuta varojen säilymisestä tai niiden käytettävyyden takaamisesta.',
|
||||||
no_transactions: 'Lompakossa ei ole yhtään tapahtumaa',
|
no_transactions: 'Lompakossa ei ole yhtään tapahtumaa',
|
||||||
manage: 'Hallinnointi',
|
manage: 'Hallinnointi',
|
||||||
exchanges: 'Pörssit',
|
exchanges: 'Vaihtokurssit',
|
||||||
extensions: 'Laajennukset',
|
extensions: 'Laajennukset',
|
||||||
no_extensions: 'Laajennuksia ei ole asennettu :(',
|
no_extensions: 'Laajennuksia ei ole asennettu :(',
|
||||||
created: 'Luotu',
|
created: 'Luotu',
|
||||||
@@ -124,7 +125,7 @@ window.localisation.fi = {
|
|||||||
extension_sources: 'Laajennuslähteet',
|
extension_sources: 'Laajennuslähteet',
|
||||||
ext_sources_hint: 'Lähteet joista laajennuksia voi ladata',
|
ext_sources_hint: 'Lähteet joista laajennuksia voi ladata',
|
||||||
ext_sources_label:
|
ext_sources_label:
|
||||||
'Lähde-URL (käytä vain virallista LNbits ja luotettavia laajennuslähteitä)',
|
'Lähde-URL (käytä vain virallista LNbits tai muuta luotettaa laajennuslähdettä)',
|
||||||
warning: 'Varoitus',
|
warning: 'Varoitus',
|
||||||
repository: 'Laajennuksien lähde',
|
repository: 'Laajennuksien lähde',
|
||||||
confirm_continue: 'Haluatko varmasti jatkaa?',
|
confirm_continue: 'Haluatko varmasti jatkaa?',
|
||||||
@@ -168,11 +169,12 @@ window.localisation.fi = {
|
|||||||
tag: 'Tunniste',
|
tag: 'Tunniste',
|
||||||
unit: 'Yksikkö',
|
unit: 'Yksikkö',
|
||||||
description: 'Kuvaus',
|
description: 'Kuvaus',
|
||||||
expiry: 'Vanheneminen',
|
expiry: 'Vanhenee',
|
||||||
webhook: 'Webhook',
|
webhook: 'Webhook',
|
||||||
payment_proof: 'Maksun varmenne',
|
payment_proof: 'Maksun varmenne',
|
||||||
update: 'Päivitä',
|
update: 'Päivitä',
|
||||||
update_available: 'Saatavilla on päivitys {version}-versioon!',
|
update_available: 'Saatavilla on päivitys {version}-versioon!',
|
||||||
|
update_available: 'Rahoituslähteet',
|
||||||
latest_update:
|
latest_update:
|
||||||
'Käytössä oleva versio {version}, on viimeisin saatavilla oleva.',
|
'Käytössä oleva versio {version}, on viimeisin saatavilla oleva.',
|
||||||
notifications: 'Tiedotteet',
|
notifications: 'Tiedotteet',
|
||||||
@@ -182,7 +184,7 @@ window.localisation.fi = {
|
|||||||
notifications_enable_nostr_desc: 'Lähetä tietodukset Nostr:in kautta',
|
notifications_enable_nostr_desc: 'Lähetä tietodukset Nostr:in kautta',
|
||||||
notifications_nostr_private_key: 'Nostr-yksityisavain',
|
notifications_nostr_private_key: 'Nostr-yksityisavain',
|
||||||
notifications_nostr_private_key_desc:
|
notifications_nostr_private_key_desc:
|
||||||
'Yksityinen avain (hex tai nsec) Nostr viestien lähettämisen allekirjoitukseen',
|
'Yksityinen avain (hex tai nsec) Nostr-viestien lähettämisen allekirjoitukseen',
|
||||||
notifications_nostr_identifiers: 'Nostr-tunnisteet',
|
notifications_nostr_identifiers: 'Nostr-tunnisteet',
|
||||||
notifications_nostr_identifiers_desc:
|
notifications_nostr_identifiers_desc:
|
||||||
'Lista tunnisteista kenelle tiedotukset lähetetään',
|
'Lista tunnisteista kenelle tiedotukset lähetetään',
|
||||||
@@ -195,6 +197,25 @@ window.localisation.fi = {
|
|||||||
notifications_chat_id: 'Keskustelun tunnus',
|
notifications_chat_id: 'Keskustelun tunnus',
|
||||||
notifications_chat_id_desc: 'Keskustelun tunnus minne tiedotukset lähetetään',
|
notifications_chat_id_desc: 'Keskustelun tunnus minne tiedotukset lähetetään',
|
||||||
|
|
||||||
|
notifications_email_config: 'Sähköposti määritykset',
|
||||||
|
notifications_enable_email: 'Käytä sähköpostia',
|
||||||
|
notifications_enable_email_desc: 'Lähetä tiedotteet sähköpostilla',
|
||||||
|
notifications_send_test_email: 'Lähetä testiposti',
|
||||||
|
notifications_send_email: 'Lähetä sähköpostiosoitteella',
|
||||||
|
notifications_send_email_desc: 'Lähettäjänä näkyvä sähköpostiosoite',
|
||||||
|
notifications_send_email_username: 'Käyttäjätunnus',
|
||||||
|
notifications_send_email_username_desc:
|
||||||
|
'Käyttäjätunnus, mikäli tyhjä, käytetään sähköpostiosoitetta',
|
||||||
|
notifications_send_email_password: 'Lähtevän sähköpostin salasana',
|
||||||
|
notifications_send_email_password_desc: 'Salasana lähettävälle sähköpostille',
|
||||||
|
notifications_send_email_server_port: 'Lähtevän sähköpostin SMTP-portti',
|
||||||
|
notifications_send_email_server_port_desc: 'SMTP-palvelimen portti',
|
||||||
|
notifications_send_email_server: 'Lähtevän sähköpostin SMTP-palvelin',
|
||||||
|
notifications_send_email_server_desc:
|
||||||
|
'SMTP-palvelin jonka kautta sähköpostit lähetetään',
|
||||||
|
notifications_send_to_emails: 'Sähköpostien vastaanottaja',
|
||||||
|
notifications_send_to_emails_desc: 'Kenelle sähköpostit lähetetään',
|
||||||
|
|
||||||
notification_settings_update: 'Asetuksia päivitetty',
|
notification_settings_update: 'Asetuksia päivitetty',
|
||||||
notification_settings_update_desc:
|
notification_settings_update_desc:
|
||||||
'Tiedota kun palvelimen asetuksia on päivitetty',
|
'Tiedota kun palvelimen asetuksia on päivitetty',
|
||||||
@@ -213,15 +234,15 @@ window.localisation.fi = {
|
|||||||
|
|
||||||
notification_incoming_payment: 'Saapuvat maksut',
|
notification_incoming_payment: 'Saapuvat maksut',
|
||||||
notification_incoming_payment_desc:
|
notification_incoming_payment_desc:
|
||||||
'Tiedota kun lompakko vastaanottaa ja saapuvan maksun määrä ylittää rajan (sat)',
|
'Tiedota kun lompakon vastaanottaman ja saapuvan maksun määrä ylittää rajan (sat)',
|
||||||
|
|
||||||
notification_outgoing_payment: 'Lähtevät maksut',
|
notification_outgoing_payment: 'Lähtevät maksut',
|
||||||
notification_outgoing_payment_desc:
|
notification_outgoing_payment_desc:
|
||||||
'Tiedota kun lompakko lähettää ja lähtevän maksun määrä ylittää rajan (sat)',
|
'Tiedota kun lompakon lähettävän ja maksettavan maksun määrä ylittää rajan (sat)',
|
||||||
|
|
||||||
notification_credit_debit: 'Hyvitä / Veloita',
|
notification_credit_debit: 'Hyvitys / Veloitus',
|
||||||
notification_credit_debit_desc:
|
notification_credit_debit_desc:
|
||||||
'Tiedota kun lompakkoa Superuser hyvittää tai veloittaa lompakkoa',
|
'Tiedota kun Superuser tekee lompakon hyvitys- tai veloitustapahtumia',
|
||||||
|
|
||||||
notification_balance_delta_changed: 'Saldon määrän muutos',
|
notification_balance_delta_changed: 'Saldon määrän muutos',
|
||||||
notification_balance_delta_changed_desc:
|
notification_balance_delta_changed_desc:
|
||||||
@@ -235,7 +256,7 @@ window.localisation.fi = {
|
|||||||
'Tällä määritetään kuinka usein taustatoiminto tarkistaa varojen Delta-muutokset [node_balance - lnbits_balance] killswitch-signaalille. Hakujen väli ilmoitetaan minuutteina.',
|
'Tällä määritetään kuinka usein taustatoiminto tarkistaa varojen Delta-muutokset [node_balance - lnbits_balance] killswitch-signaalille. Hakujen väli ilmoitetaan minuutteina.',
|
||||||
watchdog_delta: 'Watchdog Delta',
|
watchdog_delta: 'Watchdog Delta',
|
||||||
watchdog_delta_desc:
|
watchdog_delta_desc:
|
||||||
'Mikäli rahoituslähteen saldo laskee alle LNbits kokonaissaldon, muutetaan Rahoituslähteeksi muutetaan heti VoidWallet. Päivittämisen jälkeen asetus pitää päivittää manuaalisestsi.',
|
'Mikäli rahoituslähteen saldo laskee alle LNbits kokonaissaldon, muutetaan rahoituslähteeksi heti VoidWallet. Päivittämisen jälkeen asetus pitää päivittää manuaalisestsi.',
|
||||||
status: 'Tilanne',
|
status: 'Tilanne',
|
||||||
notification_source: 'Tiedotteiden lähde',
|
notification_source: 'Tiedotteiden lähde',
|
||||||
notification_source_label:
|
notification_source_label:
|
||||||
@@ -245,38 +266,38 @@ window.localisation.fi = {
|
|||||||
releases: 'Julkaisut',
|
releases: 'Julkaisut',
|
||||||
watchdog: 'Watchdog',
|
watchdog: 'Watchdog',
|
||||||
server_logs: 'Palvelimen lokit',
|
server_logs: 'Palvelimen lokit',
|
||||||
ip_blocker: 'IP-suodatin',
|
ip_blocker: 'Palvelimen suojaus IP-osoitesuodattimella',
|
||||||
security: 'Turvallisuus',
|
security: 'Turvallisuus',
|
||||||
security_tools: 'Turvallisuus työkalut',
|
security_tools: 'Turvallisuus työkalut',
|
||||||
block_access_hint: 'Estä pääsy IP-osoitteen perusteella',
|
block_access_hint: 'Estä pääsy IP-osoitteen perusteella',
|
||||||
allow_access_hint: 'Salli pääsy IP-osoitteen perusteella (ohittaa estot)',
|
allow_access_hint: 'Salli pääsy IP-osoitteen perusteella (ohittaa estot)',
|
||||||
enter_ip: 'Anna IP ja paina +',
|
enter_ip: 'Anna IP ja paina +',
|
||||||
rate_limiter: 'Toiston rajoitin',
|
rate_limiter: 'Toiston rajoitin',
|
||||||
callback_url_rules: 'Callback URL säännöt',
|
callback_url_rules: 'Callback URL -säännöt',
|
||||||
enter_callback_url_rule: 'Anna URL säätö regex-muodossa ja paina enter',
|
enter_callback_url_rule: 'Anna URL-sääntö regex-muodossa ja paina enter',
|
||||||
callback_url_rule_hint:
|
callback_url_rule_hint:
|
||||||
'Callback URL:it (kuten LNURL) tarkistetaan näiden sääntöjen mukaisesti. Jos sääntöjä ei ole määritetty, kaikki URL:it ovat sallittuja.',
|
'Callback URL:it (kuten LNURL) tarkistetaan näiden sääntöjen mukaisesti. Jos sääntöjä ei ole määritetty, kaikki URL:it ovat sallittuja.',
|
||||||
wallet_limiter: 'Lompakon käyttörajoitin',
|
wallet_limiter: 'Lompakon käyttörajoitin',
|
||||||
wallet_config: 'Wallet Config',
|
wallet_config: 'Wallet Config',
|
||||||
wallet_charts: 'Wallet Charts',
|
wallet_charts: 'Wallet Charts',
|
||||||
wallet_limit_max_withdraw_per_day:
|
wallet_limit_max_withdraw_per_day:
|
||||||
'Päivittäinen lompakosta nostettavissa oleva määrä satosheissa (0 poistaa käytöstä)',
|
'Päivittäin nostettavissa sat maksimi (0 poistaa käytöstä)',
|
||||||
wallet_max_ballance: 'Lompakon maksimisaldo satosheina (0 poistaa käytöstä)',
|
wallet_max_ballance: 'Maksimisaldo (sat) (0 poistaa käytöstä)',
|
||||||
wallet_limit_secs_between_trans:
|
wallet_limit_secs_between_trans:
|
||||||
'Min sekuntia transaktioiden välillä lompakkoa kohden (0 poistaa käytöstä)',
|
'Tapahtumien välinen minimi (sec) (0 poistaa käytöstä)',
|
||||||
only_incoming_payments_allowed: 'Only incoming payments allowed',
|
only_incoming_payments_allowed: 'Vain saapuvat maksut sallittuna',
|
||||||
disable_outgoing_payments: 'Disable outgoing payments',
|
disable_outgoing_payments: 'Poista lähtevät maksut käytöstä',
|
||||||
number_of_requests: 'Pyyntöjen lukumäärä',
|
number_of_requests: 'Pyyntöjen lukumäärä',
|
||||||
time_unit: 'aikayksikkö',
|
time_unit: 'aikayksikkö',
|
||||||
minute: 'minuutti',
|
minute: 'minuutti',
|
||||||
settings: 'Settings',
|
settings: 'Asetukset',
|
||||||
second: 'sekunti',
|
second: 'sekunti',
|
||||||
hour: 'tunti',
|
hour: 'tunti',
|
||||||
disable_server_log: 'Poista palvelimen loki käytöstä',
|
disable_server_log: 'Piilota palvelimen loki',
|
||||||
enable_server_log: 'Ota palvelimen loki käyttöön',
|
enable_server_log: 'Näytä palvelimen loki',
|
||||||
coming_soon: 'Ominaisuus on tulossa pian',
|
coming_soon: 'Ominaisuus on tulossa pian',
|
||||||
session_has_expired: 'Käyttämätön sessio on vanhentunut. Kirjaudu uudelleen.',
|
session_has_expired: 'Käyttämätön sessio on vanhentunut. Kirjaudu uudelleen.',
|
||||||
instant_access_question: 'tai kirjaudu suoraan',
|
instant_access_question: 'perinteinen kirjautuminen',
|
||||||
login_with_user_id: 'Kirjaudu käyttäjä-ID:llä',
|
login_with_user_id: 'Kirjaudu käyttäjä-ID:llä',
|
||||||
or: 'tai',
|
or: 'tai',
|
||||||
create_new_wallet: 'Avaa uusi lompakko',
|
create_new_wallet: 'Avaa uusi lompakko',
|
||||||
@@ -293,11 +314,13 @@ window.localisation.fi = {
|
|||||||
password: 'Anna uusi salasana',
|
password: 'Anna uusi salasana',
|
||||||
password_config: 'Salasanan määritys',
|
password_config: 'Salasanan määritys',
|
||||||
password_repeat: 'Toista uusi salasana',
|
password_repeat: 'Toista uusi salasana',
|
||||||
|
update_password: 'Päivitä salasana',
|
||||||
change_password: 'Vaihda salasana',
|
change_password: 'Vaihda salasana',
|
||||||
update_credentials: 'Päivitä käyttöoikeustiedot',
|
update_credentials: 'Päivitä käyttöoikeustiedot',
|
||||||
update_pubkey: 'Päivitä julkinen avain',
|
update_pubkey: 'Päivitä julkinen avain',
|
||||||
set_password: 'Aseta salasana',
|
set_password: 'Aseta salasana',
|
||||||
invalid_password: 'Salasanassa tulee olla vähintään kahdeksan merkkiä',
|
invalid_password: 'Salasanassa tulee olla vähintään kahdeksan merkkiä',
|
||||||
|
invalid_password_repeat: 'Salasanat eivät täsmää',
|
||||||
login: 'Kirjaudu',
|
login: 'Kirjaudu',
|
||||||
register: 'Rekisteröidy',
|
register: 'Rekisteröidy',
|
||||||
username: 'Käyttäjänimi',
|
username: 'Käyttäjänimi',
|
||||||
@@ -312,7 +335,8 @@ window.localisation.fi = {
|
|||||||
update_account: 'Päivitä tiliä',
|
update_account: 'Päivitä tiliä',
|
||||||
invalid_username: 'Virheellinen käyttäjänimi',
|
invalid_username: 'Virheellinen käyttäjänimi',
|
||||||
auth_provider: 'Tunnistamisen toimittaja',
|
auth_provider: 'Tunnistamisen toimittaja',
|
||||||
my_account: 'Minun tili',
|
my_account: 'Tilini',
|
||||||
|
existing_account_question: 'Onkohan sinulla jo tili?',
|
||||||
background_image: 'Taustakuva',
|
background_image: 'Taustakuva',
|
||||||
back: 'Takaisin',
|
back: 'Takaisin',
|
||||||
logout: 'Poistu',
|
logout: 'Poistu',
|
||||||
@@ -358,7 +382,7 @@ window.localisation.fi = {
|
|||||||
welcome_lnbits: 'Tervetuloa LNbits-palveluun',
|
welcome_lnbits: 'Tervetuloa LNbits-palveluun',
|
||||||
setup_su_account: 'Määritä Superuser-tili alta.',
|
setup_su_account: 'Määritä Superuser-tili alta.',
|
||||||
create_ticker_converter: 'Luo valuuttamuuntimen Ticker',
|
create_ticker_converter: 'Luo valuuttamuuntimen Ticker',
|
||||||
enable_audit: 'Ota auditointi käyttöön',
|
enable_audit: 'Ota seuranta käyttöön',
|
||||||
recommended: 'Suositeltu',
|
recommended: 'Suositeltu',
|
||||||
audit_desc: 'Tallenna HTTP-pyyntöjä seuraavien suodattimien mukaisesti',
|
audit_desc: 'Tallenna HTTP-pyyntöjä seuraavien suodattimien mukaisesti',
|
||||||
audit_record_req: 'Tallenna pyynnön Body',
|
audit_record_req: 'Tallenna pyynnön Body',
|
||||||
@@ -366,16 +390,16 @@ window.localisation.fi = {
|
|||||||
audit_record_req_warning_1:
|
audit_record_req_warning_1:
|
||||||
'Luottamukselliset tiedot (kuten salasanat) tallennetaan.',
|
'Luottamukselliset tiedot (kuten salasanat) tallennetaan.',
|
||||||
audit_record_req_warning_2: 'Body-datamäätä voi olla iso.',
|
audit_record_req_warning_2: 'Body-datamäätä voi olla iso.',
|
||||||
audit_record_use: 'Käytä sitä varoen.',
|
audit_record_use: 'Käytä varoen!',
|
||||||
audit_ip: 'Tallenna IP-osoite',
|
audit_ip: 'Tallenna IP-osoite',
|
||||||
audit_ip_desc: 'Tallenna asiakkaan IP-osoite',
|
audit_ip_desc: 'Tallenna asiakkaan IP-osoite',
|
||||||
audit_path_params: 'Tallenna Path-parametrit',
|
audit_path_params: 'Tallenna Path-parametrit',
|
||||||
audit_query_params: 'Tallenna Query-parametrit',
|
audit_query_params: 'Tallenna Query-parametrit',
|
||||||
audit_http_methods: 'Sisällytä HTTP-menetelmät',
|
audit_http_methods: 'Tallenna HTTP-menetelmät',
|
||||||
audit_http_methods_hint:
|
audit_http_methods_hint:
|
||||||
'Luettelo mukaan otettavista HTTP-menetelmistä. Tyhjä luettelo tallettaa kaikki.',
|
'Luettelo mukaan otettavista HTTP-menetelmistä. Tyhjä luettelo tallettaa kaikki.',
|
||||||
audit_http_methods_label: 'HTTP-metodit',
|
audit_http_methods_label: 'HTTP-metodit',
|
||||||
audit_resp_codes: 'Sisällytä HTTP-vastauskoodit',
|
audit_resp_codes: 'Tallenna HTTP-vastauskoodit',
|
||||||
audit_resp_codes_hint:
|
audit_resp_codes_hint:
|
||||||
'HTTP-koodien lista, jotka sisällytetään (regex-match). Tyhjä luettelo tallettaa kaikki. Esim: 4.*, 5.*',
|
'HTTP-koodien lista, jotka sisällytetään (regex-match). Tyhjä luettelo tallettaa kaikki. Esim: 4.*, 5.*',
|
||||||
audit_resp_codes_label: 'HTTP-vastauskoodi (säännöllinen lauseke)',
|
audit_resp_codes_label: 'HTTP-vastauskoodi (säännöllinen lauseke)',
|
||||||
@@ -410,7 +434,7 @@ window.localisation.fi = {
|
|||||||
funding_reserve_percent: 'Omavaraisuusaste: {percent} %',
|
funding_reserve_percent: 'Omavaraisuusaste: {percent} %',
|
||||||
node_managment: 'Solmun hallinta',
|
node_managment: 'Solmun hallinta',
|
||||||
node_management_not_supported:
|
node_management_not_supported:
|
||||||
'Valittu rahoituslähde ei mahdollista solmun hallintaan.',
|
'Solmun hallinta ei ole mahdollista valitun rahoituslähteen kanssa.',
|
||||||
toggle_node_ui: 'Solmun käyttöliittymä',
|
toggle_node_ui: 'Solmun käyttöliittymä',
|
||||||
toggle_public_node_ui: 'Julkinen näkymä solmun tietoihin',
|
toggle_public_node_ui: 'Julkinen näkymä solmun tietoihin',
|
||||||
toggle_transactions_node_ui:
|
toggle_transactions_node_ui:
|
||||||
@@ -418,17 +442,21 @@ window.localisation.fi = {
|
|||||||
invoice_expiry: 'Laskun vanhenemisaika',
|
invoice_expiry: 'Laskun vanhenemisaika',
|
||||||
invoice_expiry_label: 'Laskun vanhentuminen (sekunteina)',
|
invoice_expiry_label: 'Laskun vanhentuminen (sekunteina)',
|
||||||
fee_reserve: 'Kuluvaraus',
|
fee_reserve: 'Kuluvaraus',
|
||||||
fee_reserve_msats: 'Kuluvaraus milli-sat',
|
|
||||||
fee_reserve_percent: 'Kuluvaraus prosentteina',
|
fee_reserve_percent: 'Kuluvaraus prosentteina',
|
||||||
|
fee_reserve_msats: 'Kuluvaraus milli-sat',
|
||||||
|
reserve_fee_in_percent: 'Kuluvaraus prosentteina',
|
||||||
|
payment_wait_time: 'Maksun odotusaika',
|
||||||
|
payment_wait_time_desc:
|
||||||
|
'Kuinka pitkään maksua odotetaan saapuvaksi ennenkuin se merkitään Odotetaan -tilaan. Aseta pidemmäksi käytettäessä HODL-laskuja, Boltz-palvelua tms',
|
||||||
server_management: 'Palvelimen hallinta',
|
server_management: 'Palvelimen hallinta',
|
||||||
base_url: 'Perus-URL',
|
base_url: 'Palvelimen URL-osoite',
|
||||||
base_url_label: 'Staattinen/pohjan URL palvelimelle',
|
base_url_label: 'Palvelun staattinen pohja-URL',
|
||||||
authentication: 'Todennus',
|
authentication: 'Käyttäjän todennus',
|
||||||
auth_token_expiry_label: 'Tunnuksen vanhentumisaika minuutteina',
|
auth_token_expiry_label: 'Kirjautumisen vanhentumisaika minuutteina',
|
||||||
auth_token_expiry_hint: 'Aika minuuteissa, kunnes tunnus vanhenee',
|
auth_token_expiry_hint: 'Aika minuuteissa, jossa kirjautuminen vanhenee',
|
||||||
auth_allowed_methods_label: 'Sallitut kirjautumismenetelmät',
|
auth_allowed_methods_label: 'Sallitut kirjautumismenetelmät',
|
||||||
auth_allowed_methods_hint: 'Valitse kirjautumismenetelmät',
|
auth_allowed_methods_hint: 'Valitse kirjautumismenetelmät',
|
||||||
auth_nostr_label: 'Nostr Request URL',
|
auth_nostr_label: 'Nostr kutsujen URL',
|
||||||
auth_nostr_hint:
|
auth_nostr_hint:
|
||||||
'Asiakkaiden kirjautumiseen käyttämä absoluuttinen URL-osoite.',
|
'Asiakkaiden kirjautumiseen käyttämä absoluuttinen URL-osoite.',
|
||||||
auth_google_ci_label: 'Google-asiakastunnus',
|
auth_google_ci_label: 'Google-asiakastunnus',
|
||||||
@@ -446,27 +474,33 @@ window.localisation.fi = {
|
|||||||
auth_keycloak_cs_label: 'Keycloak-asiakassalasana',
|
auth_keycloak_cs_label: 'Keycloak-asiakassalasana',
|
||||||
currency_settings: 'Valuutta-asetukset',
|
currency_settings: 'Valuutta-asetukset',
|
||||||
allowed_currencies: 'Käytettävät valuutat',
|
allowed_currencies: 'Käytettävät valuutat',
|
||||||
allowed_currencies_hint:
|
allowed_currencies_hint: 'Valitse käytettävissä olevat fiat-valuutat',
|
||||||
'Rajoita käytettävissä olevien fiat-valuuttojen määrää',
|
|
||||||
default_account_currency: 'Tilin oletusvaluutta',
|
default_account_currency: 'Tilin oletusvaluutta',
|
||||||
default_account_currency_hint: 'Kirjanpidon oletusvaluutta',
|
default_account_currency_hint: 'Kirjanpidon oletusvaluutta',
|
||||||
service_fee: 'Palvelumaksu',
|
|
||||||
|
max_incoming_payment_amount: 'Saapuvan maksun enimmäismäärä',
|
||||||
|
max_incoming_payment_amount_desc: 'Enimmäismäärä jonka voi laskuttaa',
|
||||||
|
max_outgoing_payment_amount: 'Lähtevän maksun enimmäismäärä',
|
||||||
|
max_outgoing_payment_amount_desc: 'Enimmäismäärä jonka voi maksaa',
|
||||||
|
service_fee: 'Palvelumaksut',
|
||||||
service_fee_label: 'Palvelumaksu (%)',
|
service_fee_label: 'Palvelumaksu (%)',
|
||||||
service_fee_hint: 'Tapahtumastakohtainen palvelumaksu (%)',
|
service_fee_hint: 'Tapahtumastakohtainen palvelumaksu (%)',
|
||||||
service_fee_max: 'Palvelumaksun enimmäismäärä',
|
service_fee_max: 'Palvelumaksun enimmäismäärä',
|
||||||
service_fee_max_label: 'Palvelumaksu enintään (sat)',
|
service_fee_max_label: 'Palvelumaksu max (sat)',
|
||||||
service_fee_max_hint: 'Suurin veloitettava palvelumaksu (sat)',
|
service_fee_max_hint: 'Suurin veloitettava palvelumaksu (sat)',
|
||||||
fee_wallet: 'Palvelumaksujen lompakko',
|
fee_wallet: 'Palvelumaksujen lompakko',
|
||||||
fee_wallet_label: 'Palvelumaksujen lompakko (lompakon tunnus)',
|
fee_wallet_label: 'Palvelumaksujen tilityslompakko (lompakon tunnus)',
|
||||||
fee_wallet_hint: 'Lompakon tunnus, johon lähetetään maksut lähtetetään',
|
fee_wallet_hint: 'Lompakon tunnus, johon palvelumaksut tilitetään',
|
||||||
disable_fee: 'Poista maksu käytöstä',
|
disable_fee: 'Poista maksu käytöstä',
|
||||||
disable_fee_internal: 'Poista palvelumaksu sisäisiltä maksuilta',
|
disable_fee_internal: 'Poista palvelumaksu sisäisiltä maksuilta',
|
||||||
disable_fee_internal_desc: 'Poista palvelumaksu sisäisiltä salamamaksuilta',
|
disable_fee_internal_desc: 'Poista palvelumaksu sisäisiltä salamaksuilta',
|
||||||
ui_management: 'Käyttöliittymän hallinta',
|
ui_management: 'Käyttöliittymän hallinta',
|
||||||
ui_site_title: 'Sivuston nimi',
|
ui_site_title: 'Sivuston nimi',
|
||||||
|
ui_changing_remove_lnbits_elements:
|
||||||
|
' (tämän muuttamalla LNbits elementit poistuvat kotisivulla ja alareunasta)',
|
||||||
ui_site_tagline: 'Sivuston iskulause',
|
ui_site_tagline: 'Sivuston iskulause',
|
||||||
ui_elements_enable: 'Ota käyttöön elementit etusivulla',
|
ui_elements_enable: 'Ota käyttöön elementit etusivulla/alareunassa',
|
||||||
ui_elements_disable: 'Poista elementit käytöstä etusivulla',
|
ui_elements_disable: 'Poista elementit käytöstä etusivulla/alareunassa',
|
||||||
ui_toggle_elements_tip: "Poista kotisivuelementit kuten 'toimii' jne.",
|
ui_toggle_elements_tip: "Poista kotisivuelementit kuten 'toimii' jne.",
|
||||||
ui_site_description: 'Sivuston kuvaus',
|
ui_site_description: 'Sivuston kuvaus',
|
||||||
ui_site_description_hint:
|
ui_site_description_hint:
|
||||||
@@ -476,10 +510,11 @@ window.localisation.fi = {
|
|||||||
lnbits_wallet: 'LNbits-lompakko',
|
lnbits_wallet: 'LNbits-lompakko',
|
||||||
denomination: 'Valuutan nimi',
|
denomination: 'Valuutan nimi',
|
||||||
denomination_hint: 'FakeWallet-lompakon valuutan nimi',
|
denomination_hint: 'FakeWallet-lompakon valuutan nimi',
|
||||||
ui_qr_code_logo: 'QR-koodin logo',
|
denomination_error: 'Valuutta tunnisssa on oltava 3 merkkiä, tai `sat`',
|
||||||
|
ui_qr_code_logo: 'QR- ja Favicon-logo',
|
||||||
ui_qr_code_logo_hint:
|
ui_qr_code_logo_hint:
|
||||||
'Anna QR-koodissa käytettävää logo-kuvaan osoittava URL',
|
'Anna QR-koodissa ja Faviconissa käytettävän logo-kuvan URL',
|
||||||
ui_custom_image: 'Yksilöyty kuva',
|
ui_custom_image: 'Yksilöity kuva',
|
||||||
ui_custom_image_label: 'Anna yksilöidyn kuvan URL-osoite',
|
ui_custom_image_label: 'Anna yksilöidyn kuvan URL-osoite',
|
||||||
ui_custom_image_hint:
|
ui_custom_image_hint:
|
||||||
'Yksilöity kuva näytetään aloitus- ja kirjautumissivuilla',
|
'Yksilöity kuva näytetään aloitus- ja kirjautumissivuilla',
|
||||||
@@ -507,7 +542,8 @@ window.localisation.fi = {
|
|||||||
allowed_users_hint: 'Vain nämä käyttäjät voivat käyttää LNbitsiä',
|
allowed_users_hint: 'Vain nämä käyttäjät voivat käyttää LNbitsiä',
|
||||||
allowed_users_label: 'Käyttäjätunnus',
|
allowed_users_label: 'Käyttäjätunnus',
|
||||||
allow_creation_user: 'Salli uusien käyttäjien luominen',
|
allow_creation_user: 'Salli uusien käyttäjien luominen',
|
||||||
allow_creation_user_desc: 'Salli uusien käyttäjien luominen etusivulla',
|
allow_creation_user_desc: 'Etusivulta on mahdollisuus luoda uusia käyttäjiä',
|
||||||
|
new_user_not_allowed: 'Tunnusten luonti on estetty.',
|
||||||
components: 'Komponentit',
|
components: 'Komponentit',
|
||||||
long_running_endpoints: 'Top 5 pisimpään yhteydessä ollutta päätepistettä',
|
long_running_endpoints: 'Top 5 pisimpään yhteydessä ollutta päätepistettä',
|
||||||
http_request_methods: 'HTTP-pyynnön menetelmät',
|
http_request_methods: 'HTTP-pyynnön menetelmät',
|
||||||
@@ -535,6 +571,9 @@ window.localisation.fi = {
|
|||||||
reset_wallet_keys: 'Uusi API-avaimet',
|
reset_wallet_keys: 'Uusi API-avaimet',
|
||||||
reset_wallet_keys_desc:
|
reset_wallet_keys_desc:
|
||||||
'Tämän lompakon API-avaimet uusitaan. Edelliset API-avaimet lakkaavat toimimasta ja uudet luodaan niiden tilalle..',
|
'Tämän lompakon API-avaimet uusitaan. Edelliset API-avaimet lakkaavat toimimasta ja uudet luodaan niiden tilalle..',
|
||||||
view_list: 'Näytä lompakot listana',
|
view_list: 'Näytä lompakot allekain',
|
||||||
view_column: 'Näytä lompakot riveinä'
|
view_column: 'Näytä lompakot rinnakkain',
|
||||||
|
filter_payments: 'Suodata maksuja',
|
||||||
|
filter_date: 'Suodata päiväyksellä',
|
||||||
|
websocket_example: 'Websocket esimerkki'
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -4,6 +4,7 @@ window.AdminPageLogic = {
|
|||||||
return {
|
return {
|
||||||
settings: {},
|
settings: {},
|
||||||
logs: [],
|
logs: [],
|
||||||
|
library_images: [],
|
||||||
serverlogEnabled: false,
|
serverlogEnabled: false,
|
||||||
lnbits_theme_options: [
|
lnbits_theme_options: [
|
||||||
'classic',
|
'classic',
|
||||||
@@ -140,6 +141,7 @@ window.AdminPageLogic = {
|
|||||||
async created() {
|
async created() {
|
||||||
await this.getSettings()
|
await this.getSettings()
|
||||||
await this.getAudit()
|
await this.getAudit()
|
||||||
|
await this.getUploadedImages()
|
||||||
this.balance = +'{{ balance|safe }}'
|
this.balance = +'{{ balance|safe }}'
|
||||||
const hash = window.location.hash.replace('#', '')
|
const hash = window.location.hash.replace('#', '')
|
||||||
if (hash === 'exchange_providers') {
|
if (hash === 'exchange_providers') {
|
||||||
@@ -552,6 +554,65 @@ window.AdminPageLogic = {
|
|||||||
.catch(LNbits.utils.notifyApiError)
|
.catch(LNbits.utils.notifyApiError)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onImageInput(e) {
|
||||||
|
const file = e.target.files[0]
|
||||||
|
if (file) {
|
||||||
|
this.uploadImage(file)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uploadImage(file) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'POST',
|
||||||
|
'/admin/api/v1/images',
|
||||||
|
this.g.user.wallets[0].adminkey,
|
||||||
|
formData,
|
||||||
|
{headers: {'Content-Type': 'multipart/form-data'}}
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Image uploaded!',
|
||||||
|
icon: null
|
||||||
|
})
|
||||||
|
this.getUploadedImages()
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
getUploadedImages() {
|
||||||
|
LNbits.api
|
||||||
|
.request('GET', '/admin/api/v1/images', this.g.user.wallets[0].inkey)
|
||||||
|
.then(response => {
|
||||||
|
this.library_images = response.data.map(image => ({
|
||||||
|
...image,
|
||||||
|
url: `${window.origin}/${image.directory}/${image.filename}`
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
deleteImage(filename) {
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Are you sure you want to delete this image?')
|
||||||
|
.onOk(() => {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
`/admin/api/v1/images/${filename}`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Image deleted!',
|
||||||
|
icon: null
|
||||||
|
})
|
||||||
|
this.getUploadedImages()
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
})
|
||||||
|
},
|
||||||
downloadBackup() {
|
downloadBackup() {
|
||||||
window.open('/admin/api/v1/backup', '_blank')
|
window.open('/admin/api/v1/backup', '_blank')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -581,9 +581,10 @@ window.windowMixin = {
|
|||||||
query: {wal: this.g.wallet.id}
|
query: {wal: this.g.wallet.id}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const url = new URL(window.location.href)
|
this.$router.replace({
|
||||||
url.searchParams.set('wal', this.g.wallet.id)
|
path: '/wallet',
|
||||||
window.history.replaceState({}, '', url.toString())
|
query: {wal: this.g.wallet.id}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formatBalance(amount) {
|
formatBalance(amount) {
|
||||||
@@ -843,6 +844,7 @@ window.windowMixin = {
|
|||||||
? this.$q.localStorage.getItem('lnbits.darkMode')
|
? this.$q.localStorage.getItem('lnbits.darkMode')
|
||||||
: true
|
: true
|
||||||
)
|
)
|
||||||
|
Chart.defaults.color = this.$q.dark.isActive ? '#fff' : '#000'
|
||||||
this.changeTheme(this.themeChoice)
|
this.changeTheme(this.themeChoice)
|
||||||
this.applyBorder()
|
this.applyBorder()
|
||||||
if (this.$q.dark.isActive) {
|
if (this.$q.dark.isActive) {
|
||||||
|
|||||||
@@ -178,6 +178,26 @@ window.app.component('payment-list', {
|
|||||||
return LNbits.map.payment(obj)
|
return LNbits.map.payment(obj)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
.catch(err => {
|
||||||
|
this.paymentsTable.loading = false
|
||||||
|
if (g.user.admin) {
|
||||||
|
this.fetchPaymentsAsAdmin(this.currentWallet.id, params)
|
||||||
|
} else {
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fetchPaymentsAsAdmin(walletId, params) {
|
||||||
|
params = (params || '') + '&wallet_id=' + walletId
|
||||||
|
return LNbits.api
|
||||||
|
.request('GET', '/api/v1/payments/all/paginated?' + params)
|
||||||
|
.then(response => {
|
||||||
|
this.paymentsTable.loading = false
|
||||||
|
this.paymentsTable.pagination.rowsNumber = response.data.total
|
||||||
|
this.payments = response.data.data.map(obj => {
|
||||||
|
return LNbits.map.payment(obj)
|
||||||
|
})
|
||||||
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
this.paymentsTable.loading = false
|
this.paymentsTable.loading = false
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ window.UsersPageLogic = {
|
|||||||
.catch(LNbits.utils.notifyApiError)
|
.catch(LNbits.utils.notifyApiError)
|
||||||
},
|
},
|
||||||
fetchWallets(userId) {
|
fetchWallets(userId) {
|
||||||
LNbits.api
|
return LNbits.api
|
||||||
.request('GET', `/users/api/v1/user/${userId}/wallet`)
|
.request('GET', `/users/api/v1/user/${userId}/wallet`)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.wallets = res.data
|
this.wallets = res.data
|
||||||
@@ -378,8 +378,13 @@ window.UsersPageLogic = {
|
|||||||
this.activeUser.show = false
|
this.activeUser.show = false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
showPayments(wallet_id) {
|
async showWalletPayments(walletId) {
|
||||||
this.paymentsWallet = this.wallets.find(wallet => wallet.id === wallet_id)
|
this.activeUser.show = false
|
||||||
|
await this.fetchWallets(this.users[0].id)
|
||||||
|
await this.showPayments(walletId)
|
||||||
|
},
|
||||||
|
showPayments(walletId) {
|
||||||
|
this.paymentsWallet = this.wallets.find(wallet => wallet.id === walletId)
|
||||||
this.paymentPage.show = true
|
this.paymentPage.show = true
|
||||||
},
|
},
|
||||||
searchUserBy(fieldName) {
|
searchUserBy(fieldName) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ window.WalletPageLogic = {
|
|||||||
return {
|
return {
|
||||||
origin: window.location.origin,
|
origin: window.location.origin,
|
||||||
baseUrl: `${window.location.protocol}//${window.location.host}/`,
|
baseUrl: `${window.location.protocol}//${window.location.host}/`,
|
||||||
|
websocketUrl: `${'http:' ? 'ws://' : 'wss://'}${window.location.host}/api/v1/ws`,
|
||||||
parse: {
|
parse: {
|
||||||
show: false,
|
show: false,
|
||||||
invoice: null,
|
invoice: null,
|
||||||
@@ -1119,7 +1120,6 @@ window.WalletPageLogic = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'g.updatePayments'(newVal, oldVal) {
|
'g.updatePayments'(newVal, oldVal) {
|
||||||
console.log('updatePayments changed:', {newVal, oldVal})
|
|
||||||
this.parse.show = false
|
this.parse.show = false
|
||||||
if (this.receive.paymentHash === this.g.updatePaymentsHash) {
|
if (this.receive.paymentHash === this.g.updatePaymentsHash) {
|
||||||
this.receive.show = false
|
this.receive.show = false
|
||||||
@@ -1145,8 +1145,12 @@ window.WalletPageLogic = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'g.wallet': {
|
'g.wallet': {
|
||||||
handler(newWallet) {
|
handler() {
|
||||||
this.createdTasks()
|
try {
|
||||||
|
this.createdTasks()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Chart creation failed: ${error}`)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -343,6 +343,7 @@
|
|||||||
{% endblock %} {% block footer %}
|
{% endblock %} {% block footer %}
|
||||||
|
|
||||||
<q-footer
|
<q-footer
|
||||||
|
v-if="'{{ SITE_TITLE }}' == 'LNbits' && '{{ LNBITS_SHOW_HOME_PAGE_ELEMENTS }}' == 'True'"
|
||||||
class="bg-transparent q-px-lg q-py-md"
|
class="bg-transparent q-px-lg q-py-md"
|
||||||
:class="{'text-dark': !$q.dark.isActive}"
|
:class="{'text-dark': !$q.dark.isActive}"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -320,7 +320,26 @@
|
|||||||
<q-item v-if="payment.preimage">
|
<q-item v-if="payment.preimage">
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label v-text="$t('payment_proof')"></q-item-label>
|
<q-item-label v-text="$t('payment_proof')"></q-item-label>
|
||||||
<q-item-label caption v-text="payment.preimage"></q-item-label>
|
<q-item-label
|
||||||
|
caption
|
||||||
|
v-text="
|
||||||
|
`${payment.preimage.slice(0, 12)}...${payment.preimage.slice(-12)}`
|
||||||
|
"
|
||||||
|
></q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section side>
|
||||||
|
<q-item-label>
|
||||||
|
<q-icon
|
||||||
|
name="content_copy"
|
||||||
|
@click="copyText(payment.preimage)"
|
||||||
|
size="1em"
|
||||||
|
color="grey"
|
||||||
|
class="cursor-pointer"
|
||||||
|
/>
|
||||||
|
</q-item-label>
|
||||||
|
<q-tooltip>
|
||||||
|
<span v-text="payment.preimage"></span>
|
||||||
|
</q-tooltip>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
@@ -212,7 +213,7 @@ async def btc_rates(currency: str) -> list[tuple[str, float]]:
|
|||||||
try:
|
try:
|
||||||
headers = {"User-Agent": settings.user_agent}
|
headers = {"User-Agent": settings.user_agent}
|
||||||
async with httpx.AsyncClient(headers=headers) as client:
|
async with httpx.AsyncClient(headers=headers) as client:
|
||||||
r = await client.get(url, timeout=0.5)
|
r = await client.get(url, timeout=3)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
|
|
||||||
if not provider.path:
|
if not provider.path:
|
||||||
@@ -230,11 +231,10 @@ async def btc_rates(currency: str) -> list[tuple[str, float]]:
|
|||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# OK to be in squence: fetch_price times out after 0.5 seconds
|
calls = [
|
||||||
results = [
|
fetch_price(provider) for provider in settings.lnbits_exchange_rate_providers
|
||||||
await fetch_price(provider)
|
|
||||||
for provider in settings.lnbits_exchange_rate_providers
|
|
||||||
]
|
]
|
||||||
|
results = await asyncio.gather(*calls)
|
||||||
|
|
||||||
return [r for r in results if r is not None]
|
return [r for r in results if r is not None]
|
||||||
|
|
||||||
|
|||||||
+10
-6
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from typing import AsyncGenerator, Dict, Optional, Set
|
from typing import AsyncGenerator, Optional
|
||||||
|
|
||||||
from bolt11 import (
|
from bolt11 import (
|
||||||
Bolt11,
|
Bolt11,
|
||||||
@@ -34,8 +34,8 @@ class FakeWallet(Wallet):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.queue: asyncio.Queue = asyncio.Queue(0)
|
self.queue: asyncio.Queue = asyncio.Queue(0)
|
||||||
self.payment_secrets: Dict[str, str] = {}
|
self.payment_secrets: dict[str, str] = {}
|
||||||
self.paid_invoices: Set[str] = set()
|
self.paid_invoices: set[str] = set()
|
||||||
self.secret = settings.fake_wallet_secret
|
self.secret = settings.fake_wallet_secret
|
||||||
self.privkey = fake_privkey(self.secret)
|
self.privkey = fake_privkey(self.secret)
|
||||||
|
|
||||||
@@ -80,11 +80,12 @@ class FakeWallet(Wallet):
|
|||||||
secret = urandom(32).hex()
|
secret = urandom(32).hex()
|
||||||
tags.add(TagChar.payment_secret, secret)
|
tags.add(TagChar.payment_secret, secret)
|
||||||
|
|
||||||
payment_hash = sha256(secret.encode()).hexdigest()
|
preimage = urandom(32)
|
||||||
|
payment_hash = sha256(preimage).hexdigest()
|
||||||
|
|
||||||
tags.add(TagChar.payment_hash, payment_hash)
|
tags.add(TagChar.payment_hash, payment_hash)
|
||||||
|
|
||||||
self.payment_secrets[payment_hash] = secret
|
self.payment_secrets[payment_hash] = preimage.hex()
|
||||||
|
|
||||||
bolt11 = Bolt11(
|
bolt11 = Bolt11(
|
||||||
currency="bc",
|
currency="bc",
|
||||||
@@ -96,7 +97,10 @@ class FakeWallet(Wallet):
|
|||||||
payment_request = encode(bolt11, self.privkey)
|
payment_request = encode(bolt11, self.privkey)
|
||||||
|
|
||||||
return InvoiceResponse(
|
return InvoiceResponse(
|
||||||
ok=True, checking_id=payment_hash, payment_request=payment_request
|
ok=True,
|
||||||
|
checking_id=payment_hash,
|
||||||
|
payment_request=payment_request,
|
||||||
|
# preimage=preimage.hex(),
|
||||||
)
|
)
|
||||||
|
|
||||||
async def pay_invoice(self, bolt11: str, _: int) -> PaymentResponse:
|
async def pay_invoice(self, bolt11: str, _: int) -> PaymentResponse:
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ class PhoenixdWallet(Wallet):
|
|||||||
return PaymentResponse(None, None, None, None, error_message)
|
return PaymentResponse(None, None, None, None, error_message)
|
||||||
|
|
||||||
checking_id = data["paymentHash"]
|
checking_id = data["paymentHash"]
|
||||||
fee_msat = -int(data["routingFeeSat"])
|
fee_msat = -int(data["routingFeeSat"]) * 1000
|
||||||
preimage = data["paymentPreimage"]
|
preimage = data["paymentPreimage"]
|
||||||
|
|
||||||
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
|
return PaymentResponse(True, checking_id, fee_msat, preimage, None)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
|
||||||
|
"spaces": 2,
|
||||||
|
"generator-cli": {
|
||||||
|
"version": "7.12.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+1613
-7
File diff suppressed because it is too large
Load Diff
+4
-2
@@ -10,9 +10,11 @@
|
|||||||
"vendor_minify_css": "./node_modules/.bin/minify ./lnbits/static/bundle.css > ./lnbits/static/bundle.min.css",
|
"vendor_minify_css": "./node_modules/.bin/minify ./lnbits/static/bundle.css > ./lnbits/static/bundle.min.css",
|
||||||
"vendor_minify_js": "./node_modules/.bin/minify ./lnbits/static/bundle.js > ./lnbits/static/bundle.min.js",
|
"vendor_minify_js": "./node_modules/.bin/minify ./lnbits/static/bundle.js > ./lnbits/static/bundle.min.js",
|
||||||
"vendor_minify_components": "./node_modules/.bin/minify ./lnbits/static/bundle-components.js > ./lnbits/static/bundle-components.min.js",
|
"vendor_minify_components": "./node_modules/.bin/minify ./lnbits/static/bundle-components.js > ./lnbits/static/bundle-components.min.js",
|
||||||
"bundle": "npm run sass && npm run vendor_copy && npm run vendor_json && npm run vendor_bundle_css && npm run vendor_bundle_js && npm run vendor_bundle_components && npm run vendor_minify_css && npm run vendor_minify_js && npm run vendor_minify_components"
|
"bundle": "npm run sass && npm run vendor_copy && npm run vendor_json && npm run vendor_bundle_css && npm run vendor_bundle_js && npm run vendor_bundle_components && npm run vendor_minify_css && npm run vendor_minify_js && npm run vendor_minify_components",
|
||||||
|
"generate": "openapi-generator-cli generate -i openapi.json -g typescript-fetch -o ./generated"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@openapitools/openapi-generator-cli": "^2.18.4",
|
||||||
"concat": "^1.0.3",
|
"concat": "^1.0.3",
|
||||||
"minify": "^9.2.0",
|
"minify": "^9.2.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
@@ -23,10 +25,10 @@
|
|||||||
"axios": "^1.8.2",
|
"axios": "^1.8.2",
|
||||||
"chart.js": "^4.4.4",
|
"chart.js": "^4.4.4",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
|
"nostr-tools": "^2.7.2",
|
||||||
"qrcode.vue": "^3.4.1",
|
"qrcode.vue": "^3.4.1",
|
||||||
"quasar": "2.17.0",
|
"quasar": "2.17.0",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"nostr-tools": "^2.7.2",
|
|
||||||
"underscore": "^1.13.7",
|
"underscore": "^1.13.7",
|
||||||
"vue": "3.5.8",
|
"vue": "3.5.8",
|
||||||
"vue-i18n": "^10.0.6",
|
"vue-i18n": "^10.0.6",
|
||||||
|
|||||||
Generated
+12
-1
@@ -1083,6 +1083,17 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1
|
|||||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
|
||||||
typing = ["typing-extensions (>=4.8)"]
|
typing = ["typing-extensions (>=4.8)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetype"
|
||||||
|
version = "1.2.0"
|
||||||
|
description = "Infer file type and MIME type of any file/buffer. No external dependencies."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25"},
|
||||||
|
{file = "filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "greenlet"
|
name = "greenlet"
|
||||||
version = "3.0.3"
|
version = "3.0.3"
|
||||||
@@ -3403,4 +3414,4 @@ liquid = ["wallycore"]
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "~3.12 | ~3.11 | ~3.10"
|
python-versions = "~3.12 | ~3.11 | ~3.10"
|
||||||
content-hash = "96dd180aaa4fbfeb34fa6f9647c8684fce183a72b1b41d22101a9dd4b962fa2e"
|
content-hash = "f56154a228bfd11ca92c1818dd2b7d71ff67b218225103f4b701e6523ba499ed"
|
||||||
|
|||||||
+4
-1
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "lnbits"
|
name = "lnbits"
|
||||||
version = "1.0.0-rc9"
|
version = "1.0.0"
|
||||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||||
authors = ["Alan Bits <alan@lnbits.com>"]
|
authors = ["Alan Bits <alan@lnbits.com>"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -63,6 +63,8 @@ breez-sdk = {version = "0.6.6", optional = true}
|
|||||||
jsonpath-ng = "^1.7.0"
|
jsonpath-ng = "^1.7.0"
|
||||||
pynostr = "^0.6.2"
|
pynostr = "^0.6.2"
|
||||||
python-multipart = "^0.0.20"
|
python-multipart = "^0.0.20"
|
||||||
|
filetype = "^1.2.0"
|
||||||
|
|
||||||
[tool.poetry.extras]
|
[tool.poetry.extras]
|
||||||
breez = ["breez-sdk"]
|
breez = ["breez-sdk"]
|
||||||
liquid = ["wallycore"]
|
liquid = ["wallycore"]
|
||||||
@@ -145,6 +147,7 @@ module = [
|
|||||||
"fastapi_sso.sso.*",
|
"fastapi_sso.sso.*",
|
||||||
"json5.*",
|
"json5.*",
|
||||||
"jsonpath_ng.*",
|
"jsonpath_ng.*",
|
||||||
|
"filetype.*",
|
||||||
]
|
]
|
||||||
ignore_missing_imports = "True"
|
ignore_missing_imports = "True"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from lnbits.helpers import safe_upload_file_path
|
||||||
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"filepath",
|
||||||
|
[
|
||||||
|
"test.txt",
|
||||||
|
"test/test.txt",
|
||||||
|
"test/test/test.txt",
|
||||||
|
"test/../test.txt",
|
||||||
|
"*/test.txt",
|
||||||
|
"test/**/test.txt",
|
||||||
|
"./test.txt",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_safe_upload_file_path(filepath: str):
|
||||||
|
safe_path = safe_upload_file_path(filepath)
|
||||||
|
assert safe_path.name == "test.txt"
|
||||||
|
|
||||||
|
# check if subdirectories got removed
|
||||||
|
images_folder = Path(settings.lnbits_data_folder) / "images"
|
||||||
|
assert images_folder.resolve() / "test.txt" == safe_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"filepath",
|
||||||
|
[
|
||||||
|
"../test.txt",
|
||||||
|
"test/../../test.txt",
|
||||||
|
"../../test.txt",
|
||||||
|
"test/../../../test.txt",
|
||||||
|
"../../../test.txt",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_unsafe_upload_file_path(filepath: str):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
safe_upload_file_path(filepath)
|
||||||
@@ -1170,7 +1170,7 @@
|
|||||||
"pending": false,
|
"pending": false,
|
||||||
"failed": false,
|
"failed": false,
|
||||||
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"checking_id": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"fee_msat": 50,
|
"fee_msat": 30000,
|
||||||
"preimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
"preimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
},
|
},
|
||||||
"mocks": {
|
"mocks": {
|
||||||
@@ -1187,8 +1187,8 @@
|
|||||||
"response": {
|
"response": {
|
||||||
"payment_hash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"payment_hash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
"payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"msatoshi": 21000,
|
"msatoshi": 210000,
|
||||||
"msatoshi_sent": 21050,
|
"msatoshi_sent": 240000,
|
||||||
"status": "paid"
|
"status": "paid"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1206,7 +1206,7 @@
|
|||||||
"response": {
|
"response": {
|
||||||
"payment_hash": "41UmpD0E6YVZTA36uEiBT1JLHHhlmOyaY77dstcmrJY=",
|
"payment_hash": "41UmpD0E6YVZTA36uEiBT1JLHHhlmOyaY77dstcmrJY=",
|
||||||
"payment_route": {
|
"payment_route": {
|
||||||
"total_fees_msat": 50
|
"total_fees_msat": 30000
|
||||||
},
|
},
|
||||||
"payment_preimage": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
"payment_preimage": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||||
}
|
}
|
||||||
@@ -1224,7 +1224,7 @@
|
|||||||
"response_type": "json",
|
"response_type": "json",
|
||||||
"response": {
|
"response": {
|
||||||
"payment_hash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"payment_hash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"fee": 50,
|
"fee": 30000,
|
||||||
"payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
"payment_preimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1257,7 +1257,7 @@
|
|||||||
{
|
{
|
||||||
"status": {
|
"status": {
|
||||||
"type": "sent",
|
"type": "sent",
|
||||||
"feesPaid": -50,
|
"feesPaid": -30000,
|
||||||
"paymentPreimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
"paymentPreimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1287,7 +1287,7 @@
|
|||||||
"paid": true,
|
"paid": true,
|
||||||
"preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
"preimage": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"details": {
|
"details": {
|
||||||
"fee": 50
|
"fee": 30000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1303,7 +1303,7 @@
|
|||||||
"response_type": "json",
|
"response_type": "json",
|
||||||
"response": {
|
"response": {
|
||||||
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"routingFeeSat": -50,
|
"routingFeeSat": -30,
|
||||||
"paymentPreimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
"paymentPreimage": "0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1690,7 +1690,7 @@
|
|||||||
"response_type": "json",
|
"response_type": "json",
|
||||||
"response": {
|
"response": {
|
||||||
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
"paymentHash": "e35526a43d04e985594c0dfab848814f524b1c786598ec9a63beddb2d726ac96",
|
||||||
"routingFeeSat": -50
|
"routingFeeSat": -30
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user