Compare commits
31
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a9e638c1e | ||
|
|
59a2fefbf3 | ||
|
|
4725eb95c9 | ||
|
|
a5aaced420 | ||
|
|
87a6514b62 | ||
|
|
32c893027d | ||
|
|
751598e0f8 | ||
|
|
e11ccd9757 | ||
|
|
7e344de10e | ||
|
|
08fdeedffc | ||
|
|
73ba9d94a5 | ||
|
|
1640e23f75 | ||
|
|
9145c13435 | ||
|
|
41048b8cc7 | ||
|
|
b46e2c549d | ||
|
|
b382ccfdf3 | ||
|
|
1687972d89 | ||
|
|
d621a6f81f | ||
|
|
b9d00d900e | ||
|
|
0f06b7ba52 | ||
|
|
220ece6027 | ||
|
|
1663845468 | ||
|
|
5b7d1e63b5 | ||
|
|
5bd8bf2f56 | ||
|
|
a3d7b463ae | ||
|
|
e1e2112461 | ||
|
|
92fc9a130f | ||
|
|
e15f3cd207 | ||
|
|
041b358310 | ||
|
|
cbd3de88c4 | ||
|
|
156ab10ad6 |
@@ -36,6 +36,8 @@ lnurl_router = APIRouter(tags=["LNURL"])
|
|||||||
|
|
||||||
async def _handle(lnurl: str) -> LnurlResponseModel:
|
async def _handle(lnurl: str) -> LnurlResponseModel:
|
||||||
try:
|
try:
|
||||||
|
if "@" in lnurl: # lower case lightning addresses
|
||||||
|
lnurl = lnurl.lower()
|
||||||
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
|
res = await lnurl_handle(lnurl, user_agent=settings.user_agent, timeout=5)
|
||||||
if isinstance(res, LnurlErrorResponse):
|
if isinstance(res, LnurlErrorResponse):
|
||||||
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
|
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=res.reason)
|
||||||
|
|||||||
+2
-3
@@ -7,15 +7,14 @@ from typing import Any
|
|||||||
from urllib import request
|
from urllib import request
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import jinja2
|
|
||||||
import jwt
|
import jwt
|
||||||
import shortuuid
|
import shortuuid
|
||||||
from fastapi.routing import APIRoute
|
from fastapi.routing import APIRoute
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from packaging import version
|
from packaging import version
|
||||||
from pydantic.schema import field_schema
|
from pydantic.schema import field_schema
|
||||||
|
from starlette.templating import Jinja2Templates
|
||||||
|
|
||||||
from lnbits.jinja2_templating import Jinja2Templates
|
|
||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
from lnbits.utils.crypto import AESCipher
|
from lnbits.utils.crypto import AESCipher
|
||||||
from lnbits.utils.exchange_rates import currencies
|
from lnbits.utils.exchange_rates import currencies
|
||||||
@@ -66,7 +65,7 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
for f in additional_folders
|
for f in additional_folders
|
||||||
]
|
]
|
||||||
folders.extend(additional_folders)
|
folders.extend(additional_folders)
|
||||||
t = Jinja2Templates(loader=jinja2.FileSystemLoader(folders))
|
t = Jinja2Templates(directory=folders)
|
||||||
t.env.globals["static_url_for"] = static_url_for
|
t.env.globals["static_url_for"] = static_url_for
|
||||||
t.env.globals["normalize_path"] = normalize_path
|
t.env.globals["normalize_path"] = normalize_path
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import typing
|
|
||||||
|
|
||||||
from jinja2 import BaseLoader, Environment, pass_context
|
|
||||||
from starlette.datastructures import QueryParams
|
|
||||||
from starlette.requests import Request
|
|
||||||
from starlette.templating import Jinja2Templates as SuperJinja2Templates
|
|
||||||
|
|
||||||
|
|
||||||
class Jinja2Templates(SuperJinja2Templates):
|
|
||||||
def __init__(self, loader: BaseLoader) -> None:
|
|
||||||
self.env = self.get_environment(loader)
|
|
||||||
super().__init__(env=self.env)
|
|
||||||
|
|
||||||
def get_environment(self, loader: BaseLoader) -> Environment:
|
|
||||||
@pass_context
|
|
||||||
def url_for(context: dict, name: str, **path_params: typing.Any) -> str:
|
|
||||||
request: Request = context["request"]
|
|
||||||
return request.app.url_path_for(name, **path_params)
|
|
||||||
|
|
||||||
def url_params_update(init: QueryParams, **new: typing.Any) -> QueryParams:
|
|
||||||
values = dict(init)
|
|
||||||
values.update(new)
|
|
||||||
return QueryParams(**values)
|
|
||||||
|
|
||||||
env = Environment(loader=loader, autoescape=True)
|
|
||||||
env.globals["url_for"] = url_for
|
|
||||||
env.globals["url_params_update"] = url_params_update
|
|
||||||
return env
|
|
||||||
@@ -76,7 +76,7 @@ class LndRestNode(Node):
|
|||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def get(self, path: str, **kwargs):
|
def get(self, path: str, **kwargs):
|
||||||
return self.request("GET", path, **kwargs)
|
return self.request("GET", path, timeout=30, **kwargs)
|
||||||
|
|
||||||
async def _get_id(self) -> str:
|
async def _get_id(self) -> str:
|
||||||
info = await self.get("/v1/getinfo")
|
info = await self.get("/v1/getinfo")
|
||||||
@@ -99,11 +99,12 @@ class LndRestNode(Node):
|
|||||||
"perm": True,
|
"perm": True,
|
||||||
"timeout": 30,
|
"timeout": 30,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def disconnect_peer(self, peer_id: str):
|
async def disconnect_peer(self, peer_id: str):
|
||||||
try:
|
try:
|
||||||
await self.request("DELETE", "/v1/peers/" + peer_id)
|
await self.request("DELETE", "/v1/peers/" + peer_id, timeout=30)
|
||||||
except HTTPException as exc:
|
except HTTPException as exc:
|
||||||
if "unable to disconnect" in exc.detail:
|
if "unable to disconnect" in exc.detail:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -141,6 +142,7 @@ class LndRestNode(Node):
|
|||||||
"local_funding_amount": local_amount,
|
"local_funding_amount": local_amount,
|
||||||
"push_sat": push_amount,
|
"push_sat": push_amount,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
return ChannelPoint(
|
return ChannelPoint(
|
||||||
# WHY IS THIS REVERSED?!
|
# WHY IS THIS REVERSED?!
|
||||||
@@ -214,6 +216,7 @@ class LndRestNode(Node):
|
|||||||
# 'min_htlc_msat': <uint64>,
|
# 'min_htlc_msat': <uint64>,
|
||||||
# 'inbound_fee': <InboundFee>,
|
# 'inbound_fee': <InboundFee>,
|
||||||
},
|
},
|
||||||
|
timeout=30,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_channel(self, channel_id: str) -> NodeChannel | None:
|
async def get_channel(self, channel_id: str) -> NodeChannel | None:
|
||||||
|
|||||||
+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
@@ -0,0 +1,105 @@
|
|||||||
|
window.app.component('lnbits-dialog', {
|
||||||
|
template: '#lnbits-dialog',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
type: String,
|
||||||
|
default: 'top'
|
||||||
|
},
|
||||||
|
persistent: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
showCancel: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
cancelLabel: {
|
||||||
|
type: String,
|
||||||
|
default: 'Close'
|
||||||
|
},
|
||||||
|
cancelColor: {
|
||||||
|
type: String,
|
||||||
|
default: 'grey'
|
||||||
|
},
|
||||||
|
action: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
secondaryAction: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
emits: ['update:show', 'hide', 'cancel', 'action', 'secondary-action'],
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
hasAction() {
|
||||||
|
return !!(this.action && this.action.label)
|
||||||
|
},
|
||||||
|
hasSecondaryAction() {
|
||||||
|
return !!(this.secondaryAction && this.secondaryAction.label)
|
||||||
|
},
|
||||||
|
|
||||||
|
actionProps() {
|
||||||
|
const action = this.action || {}
|
||||||
|
const obj = {
|
||||||
|
label: action.label || 'Action',
|
||||||
|
color: action.color || 'primary',
|
||||||
|
loading: !!action.loading,
|
||||||
|
disable: !!action.disable,
|
||||||
|
closePopup: !!action.closePopup
|
||||||
|
}
|
||||||
|
if (action.icon) {
|
||||||
|
obj.icon = action.icon
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
},
|
||||||
|
secondaryActionProps() {
|
||||||
|
const action = this.secondaryAction || {}
|
||||||
|
const obj = {
|
||||||
|
label: action.label || 'Secondary',
|
||||||
|
color: action.color || 'secondary',
|
||||||
|
loading: !!action.loading,
|
||||||
|
disable: !!action.disable,
|
||||||
|
closePopup: !!action.closePopup
|
||||||
|
}
|
||||||
|
if (action.icon) {
|
||||||
|
obj.icon = action.icon
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
handleModelUpdate(value) {
|
||||||
|
this.$emit('update:show', value)
|
||||||
|
},
|
||||||
|
|
||||||
|
handleHide() {
|
||||||
|
this.handleModelUpdate(false)
|
||||||
|
this.$emit('hide')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleCancel() {
|
||||||
|
this.$emit('cancel')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleAction() {
|
||||||
|
this.$emit('action')
|
||||||
|
},
|
||||||
|
|
||||||
|
handleSecondaryAction() {
|
||||||
|
this.$emit('secondary-action')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
"js/components/admin/lnbits-admin-site-customisation.js",
|
"js/components/admin/lnbits-admin-site-customisation.js",
|
||||||
"js/components/admin/lnbits-admin-assets-config.js",
|
"js/components/admin/lnbits-admin-assets-config.js",
|
||||||
"js/components/admin/lnbits-admin-audit.js",
|
"js/components/admin/lnbits-admin-audit.js",
|
||||||
|
"js/components/lnbits-dialog.js",
|
||||||
"js/components/lnbits-wallet-charts.js",
|
"js/components/lnbits-wallet-charts.js",
|
||||||
"js/components/lnbits-wallet-api-docs.js",
|
"js/components/lnbits-wallet-api-docs.js",
|
||||||
"js/components/lnbits-wallet-icon.js",
|
"js/components/lnbits-wallet-icon.js",
|
||||||
|
|||||||
Vendored
+131
-114
@@ -7,13 +7,13 @@
|
|||||||
exports.noConflict = function () { global._ = current; return exports; };
|
exports.noConflict = function () { global._ = current; return exports; };
|
||||||
}()));
|
}()));
|
||||||
}(this, (function () {
|
}(this, (function () {
|
||||||
// Underscore.js 1.13.7
|
// Underscore.js 1.13.8
|
||||||
// https://underscorejs.org
|
// https://underscorejs.org
|
||||||
// (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
|
// (c) 2009-2026 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
|
||||||
// Underscore may be freely distributed under the MIT license.
|
// Underscore may be freely distributed under the MIT license.
|
||||||
|
|
||||||
// Current version.
|
// Current version.
|
||||||
var VERSION = '1.13.7';
|
var VERSION = '1.13.8';
|
||||||
|
|
||||||
// Establish the root object, `window` (`self`) in the browser, `global`
|
// Establish the root object, `window` (`self`) in the browser, `global`
|
||||||
// on the server, or `this` in some virtual machines. We use `self`
|
// on the server, or `this` in some virtual machines. We use `self`
|
||||||
@@ -357,131 +357,146 @@
|
|||||||
// We use this string twice, so give it a name for minification.
|
// We use this string twice, so give it a name for minification.
|
||||||
var tagDataView = '[object DataView]';
|
var tagDataView = '[object DataView]';
|
||||||
|
|
||||||
// Internal recursive comparison function for `_.isEqual`.
|
// Perform a deep comparison to check if two objects are equal.
|
||||||
function eq(a, b, aStack, bStack) {
|
function isEqual(a, b) {
|
||||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
var todo = [{a: a, b: b}];
|
||||||
// See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
// Initializing stacks of traversed objects for cycle detection.
|
||||||
if (a === b) return a !== 0 || 1 / a === 1 / b;
|
var aStack = [], bStack = [];
|
||||||
// `null` or `undefined` only equal to itself (strict comparison).
|
|
||||||
if (a == null || b == null) return false;
|
while (todo.length) {
|
||||||
// `NaN`s are equivalent, but non-reflexive.
|
var frame = todo.pop();
|
||||||
if (a !== a) return b !== b;
|
if (frame === true) {
|
||||||
// Exhaust primitive checks
|
// Remove the first object from the stack of traversed objects.
|
||||||
var type = typeof a;
|
aStack.pop();
|
||||||
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
bStack.pop();
|
||||||
return deepEq(a, b, aStack, bStack);
|
continue;
|
||||||
}
|
}
|
||||||
|
a = frame.a;
|
||||||
|
b = frame.b;
|
||||||
|
|
||||||
|
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
||||||
|
// See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal).
|
||||||
|
if (a === b) {
|
||||||
|
if (a !== 0 || 1 / a === 1 / b) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// `null` or `undefined` only equal to itself (strict comparison).
|
||||||
|
if (a == null || b == null) return false;
|
||||||
|
// `NaN`s are equivalent, but non-reflexive.
|
||||||
|
if (a !== a) {
|
||||||
|
if (b !== b) continue;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Exhaust primitive checks
|
||||||
|
var type = typeof a;
|
||||||
|
if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
|
||||||
|
|
||||||
// Internal recursive comparison function for `_.isEqual`.
|
// Internal recursive comparison function for `_.isEqual`.
|
||||||
function deepEq(a, b, aStack, bStack) {
|
// Unwrap any wrapped objects.
|
||||||
// Unwrap any wrapped objects.
|
if (a instanceof _$1) a = a._wrapped;
|
||||||
if (a instanceof _$1) a = a._wrapped;
|
if (b instanceof _$1) b = b._wrapped;
|
||||||
if (b instanceof _$1) b = b._wrapped;
|
// Compare `[[Class]]` names.
|
||||||
// Compare `[[Class]]` names.
|
var className = toString.call(a);
|
||||||
var className = toString.call(a);
|
if (className !== toString.call(b)) return false;
|
||||||
if (className !== toString.call(b)) return false;
|
// Work around a bug in IE 10 - Edge 13.
|
||||||
// Work around a bug in IE 10 - Edge 13.
|
if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
|
||||||
if (hasDataViewBug && className == '[object Object]' && isDataView$1(a)) {
|
if (!isDataView$1(b)) return false;
|
||||||
if (!isDataView$1(b)) return false;
|
className = tagDataView;
|
||||||
className = tagDataView;
|
}
|
||||||
}
|
switch (className) {
|
||||||
switch (className) {
|
// These types are compared by value.
|
||||||
// These types are compared by value.
|
|
||||||
case '[object RegExp]':
|
case '[object RegExp]':
|
||||||
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
|
||||||
case '[object String]':
|
case '[object String]':
|
||||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
||||||
// equivalent to `new String("5")`.
|
// equivalent to `new String("5")`.
|
||||||
return '' + a === '' + b;
|
if ('' + a === '' + b) continue;
|
||||||
|
return false;
|
||||||
case '[object Number]':
|
case '[object Number]':
|
||||||
// `NaN`s are equivalent, but non-reflexive.
|
todo.push({a: +a, b: +b});
|
||||||
// Object(NaN) is equivalent to NaN.
|
continue;
|
||||||
if (+a !== +a) return +b !== +b;
|
|
||||||
// An `egal` comparison is performed for other numeric values.
|
|
||||||
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
|
|
||||||
case '[object Date]':
|
case '[object Date]':
|
||||||
case '[object Boolean]':
|
case '[object Boolean]':
|
||||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
||||||
// millisecond representations. Note that invalid dates with millisecond representations
|
// millisecond representations. Note that invalid dates with millisecond representations
|
||||||
// of `NaN` are not equivalent.
|
// of `NaN` are not equivalent.
|
||||||
return +a === +b;
|
if (+a === +b) continue;
|
||||||
|
return false;
|
||||||
case '[object Symbol]':
|
case '[object Symbol]':
|
||||||
return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
|
if (SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b)) continue;
|
||||||
|
return false;
|
||||||
case '[object ArrayBuffer]':
|
case '[object ArrayBuffer]':
|
||||||
case tagDataView:
|
case tagDataView:
|
||||||
// Coerce to typed array so we can fall through.
|
// Coerce to typed array so we can fall through.
|
||||||
return deepEq(toBufferView(a), toBufferView(b), aStack, bStack);
|
todo.push({a: toBufferView(a), b: toBufferView(b)});
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var areArrays = className === '[object Array]';
|
var areArrays = className === '[object Array]';
|
||||||
if (!areArrays && isTypedArray$1(a)) {
|
if (!areArrays && isTypedArray$1(a)) {
|
||||||
var byteLength = getByteLength(a);
|
var byteLength = getByteLength(a);
|
||||||
if (byteLength !== getByteLength(b)) return false;
|
if (byteLength !== getByteLength(b)) return false;
|
||||||
if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true;
|
if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) continue;
|
||||||
areArrays = true;
|
areArrays = true;
|
||||||
}
|
|
||||||
if (!areArrays) {
|
|
||||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
|
||||||
|
|
||||||
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
|
||||||
// from different frames are.
|
|
||||||
var aCtor = a.constructor, bCtor = b.constructor;
|
|
||||||
if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
|
|
||||||
isFunction$1(bCtor) && bCtor instanceof bCtor)
|
|
||||||
&& ('constructor' in a && 'constructor' in b)) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
if (!areArrays) {
|
||||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
if (typeof a != 'object' || typeof b != 'object') return false;
|
||||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
|
||||||
|
|
||||||
// Initializing stack of traversed objects.
|
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
|
||||||
// It's done here since we only need them for objects and arrays comparison.
|
// from different frames are.
|
||||||
aStack = aStack || [];
|
var aCtor = a.constructor, bCtor = b.constructor;
|
||||||
bStack = bStack || [];
|
if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor &&
|
||||||
var length = aStack.length;
|
isFunction$1(bCtor) && bCtor instanceof bCtor)
|
||||||
while (length--) {
|
&& ('constructor' in a && 'constructor' in b)) {
|
||||||
// Linear search. Performance is inversely proportional to the number of
|
return false;
|
||||||
// unique nested structures.
|
}
|
||||||
if (aStack[length] === a) return bStack[length] === b;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add the first object to the stack of traversed objects.
|
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
||||||
aStack.push(a);
|
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
||||||
bStack.push(b);
|
|
||||||
|
|
||||||
// Recursively compare objects and arrays.
|
var length = aStack.length;
|
||||||
if (areArrays) {
|
|
||||||
// Compare array lengths to determine if a deep comparison is necessary.
|
|
||||||
length = a.length;
|
|
||||||
if (length !== b.length) return false;
|
|
||||||
// Deep compare the contents, ignoring non-numeric properties.
|
|
||||||
while (length--) {
|
while (length--) {
|
||||||
if (!eq(a[length], b[length], aStack, bStack)) return false;
|
// Linear search. Performance is inversely proportional to the number of
|
||||||
|
// unique nested structures.
|
||||||
|
if (aStack[length] === a) {
|
||||||
|
if (bStack[length] === b) break;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
if (length >= 0) continue;
|
||||||
// Deep compare objects.
|
|
||||||
var _keys = keys(a), key;
|
// Add the first object to the stack of traversed objects.
|
||||||
length = _keys.length;
|
aStack.push(a);
|
||||||
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
bStack.push(b);
|
||||||
if (keys(b).length !== length) return false;
|
todo.push(true);
|
||||||
while (length--) {
|
|
||||||
// Deep compare each member
|
// Recursively compare objects and arrays.
|
||||||
key = _keys[length];
|
if (areArrays) {
|
||||||
if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
|
// Compare array lengths to determine if a deep comparison is necessary.
|
||||||
|
length = a.length;
|
||||||
|
if (length !== b.length) return false;
|
||||||
|
// Deep compare the contents, ignoring non-numeric properties.
|
||||||
|
while (length--) {
|
||||||
|
todo.push({a: a[length], b: b[length]});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Deep compare objects.
|
||||||
|
var _keys = keys(a), key;
|
||||||
|
length = _keys.length;
|
||||||
|
// Ensure that both objects contain the same number of properties before comparing deep equality.
|
||||||
|
if (keys(b).length !== length) return false;
|
||||||
|
while (length--) {
|
||||||
|
// Deep compare each member
|
||||||
|
key = _keys[length];
|
||||||
|
if (!has$1(b, key)) return false;
|
||||||
|
todo.push({a: a[key], b: b[key]});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove the first object from the stack of traversed objects.
|
|
||||||
aStack.pop();
|
|
||||||
bStack.pop();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform a deep comparison to check if two objects are equal.
|
|
||||||
function isEqual(a, b) {
|
|
||||||
return eq(a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve all the enumerable property names of an object.
|
// Retrieve all the enumerable property names of an object.
|
||||||
function allKeys(obj) {
|
function allKeys(obj) {
|
||||||
if (!isObject(obj)) return [];
|
if (!isObject(obj)) return [];
|
||||||
@@ -1032,25 +1047,27 @@
|
|||||||
var isArrayLike = createSizePropertyCheck(getLength);
|
var isArrayLike = createSizePropertyCheck(getLength);
|
||||||
|
|
||||||
// Internal implementation of a recursive `flatten` function.
|
// Internal implementation of a recursive `flatten` function.
|
||||||
function flatten$1(input, depth, strict, output) {
|
function flatten$1(input, depth, strict) {
|
||||||
output = output || [];
|
if (!depth && depth !== 0) depth = Infinity;
|
||||||
if (!depth && depth !== 0) {
|
var output = [], idx = 0, i = 0, length = getLength(input) || 0, stack = [];
|
||||||
depth = Infinity;
|
while (true) {
|
||||||
} else if (depth <= 0) {
|
if (i >= length) {
|
||||||
return output.concat(input);
|
if (!stack.length) break;
|
||||||
}
|
var frame = stack.pop();
|
||||||
var idx = output.length;
|
i = frame.i;
|
||||||
for (var i = 0, length = getLength(input); i < length; i++) {
|
input = frame.v;
|
||||||
var value = input[i];
|
length = getLength(input);
|
||||||
if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
|
continue;
|
||||||
|
}
|
||||||
|
var value = input[i++];
|
||||||
|
if (stack.length >= depth) {
|
||||||
|
output[idx++] = value;
|
||||||
|
} else if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) {
|
||||||
// Flatten current level of array or arguments object.
|
// Flatten current level of array or arguments object.
|
||||||
if (depth > 1) {
|
stack.push({i: i, v: input});
|
||||||
flatten$1(value, depth - 1, strict, output);
|
i = 0;
|
||||||
idx = output.length;
|
input = value;
|
||||||
} else {
|
length = getLength(input);
|
||||||
var j = 0, len = value.length;
|
|
||||||
while (j < len) output[idx++] = value[j++];
|
|
||||||
}
|
|
||||||
} else if (!strict) {
|
} else if (!strict) {
|
||||||
output[idx++] = value;
|
output[idx++] = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ include('components/lnbits-wallet-share.vue') %} {%
|
|||||||
include('components/lnbits-wallet-charts.vue') %} {%
|
include('components/lnbits-wallet-charts.vue') %} {%
|
||||||
include('components/lnbits-wallet-paylinks.vue') %} {%
|
include('components/lnbits-wallet-paylinks.vue') %} {%
|
||||||
include('components/lnbits-wallet-extra.vue') %} {%
|
include('components/lnbits-wallet-extra.vue') %} {%
|
||||||
include('components/lnbits-error.vue') %}
|
include('components/lnbits-error.vue') %} {%
|
||||||
|
include('components/lnbits-dialog.vue') %}
|
||||||
|
|
||||||
<template id="lnbits-manage">
|
<template id="lnbits-manage">
|
||||||
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
||||||
|
|||||||
@@ -199,47 +199,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-8 col-sm-12"></div>
|
<div class="col-md-8 col-sm-12"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<lnbits-dialog
|
||||||
<q-dialog v-model="exchangeData.showTickerConversion" position="top">
|
:show="exchangeData.showTickerConversion"
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
:title="$t('create_ticker_converter')"
|
||||||
<div class="q-mb-md">
|
:action="{
|
||||||
<strong v-text="$t('create_ticker_converter')"></strong>
|
label: 'Add Ticker Conversion'
|
||||||
|
}"
|
||||||
|
:cancel-label="$t('close')"
|
||||||
|
@update:show="exchangeData.showTickerConversion = $event"
|
||||||
|
@action="addExchangeTickerConversion()"
|
||||||
|
>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 q-mb-md">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="exchangeData.convertFromTicker"
|
||||||
|
label="From Currency"
|
||||||
|
:options="currencies"
|
||||||
|
></q-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="col-12">
|
||||||
<div class="col-12 q-mb-md">
|
<q-input
|
||||||
<q-select
|
v-model="exchangeData.convertToTicker"
|
||||||
filled
|
dense
|
||||||
dense
|
filled
|
||||||
v-model="exchangeData.convertFromTicker"
|
label="New Ticker"
|
||||||
label="From Currency"
|
hint="This ticker will be used for the exchange API calls."
|
||||||
:options="currencies"
|
>
|
||||||
></q-select>
|
</q-input>
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="exchangeData.convertToTicker"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
label="New Ticker"
|
|
||||||
hint="This ticker will be used for the exchange API calls."
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
</div>
|
||||||
<q-btn
|
</lnbits-dialog>
|
||||||
@click="addExchangeTickerConversion()"
|
|
||||||
label="Add Ticker Conversion"
|
|
||||||
color="primary"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('close')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -125,15 +125,14 @@
|
|||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</div>
|
</div>
|
||||||
</q-list>
|
</q-list>
|
||||||
<q-dialog v-model="showQRDialog">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md">
|
:show="showQRDialog"
|
||||||
<q-card-section>
|
:position="'standard'"
|
||||||
<lnbits-qrcode :value="qrValue"></lnbits-qrcode>
|
@update:show="showQRDialog = $event"
|
||||||
</q-card-section>
|
>
|
||||||
<q-card-actions align="right">
|
<q-card-section>
|
||||||
<q-btn flat :label="$t('close')" v-close-popup />
|
<lnbits-qrcode :value="qrValue"></lnbits-qrcode>
|
||||||
</q-card-actions>
|
</q-card-section>
|
||||||
</q-card>
|
</lnbits-dialog>
|
||||||
</q-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<template id="lnbits-dialog">
|
||||||
|
<q-dialog
|
||||||
|
:model-value="show"
|
||||||
|
:position="position"
|
||||||
|
:persistent="persistent"
|
||||||
|
@update:model-value="handleModelUpdate"
|
||||||
|
@hide="handleHide"
|
||||||
|
>
|
||||||
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
|
<!-- Header with optional title -->
|
||||||
|
<q-card-section v-if="title">
|
||||||
|
<div class="text-h6" v-text="title"></div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<!-- Main content slot -->
|
||||||
|
<q-card-section>
|
||||||
|
<slot></slot>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions
|
||||||
|
v-if="showCancel || hasAction || hasSecondaryAction"
|
||||||
|
class="q-mt-lg"
|
||||||
|
align="right"
|
||||||
|
>
|
||||||
|
<q-btn
|
||||||
|
v-if="showCancel"
|
||||||
|
v-close-popup
|
||||||
|
flat
|
||||||
|
:color="cancelColor"
|
||||||
|
:label="cancelLabel"
|
||||||
|
@click="handleCancel"
|
||||||
|
></q-btn>
|
||||||
|
|
||||||
|
<q-space></q-space>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="hasSecondaryAction"
|
||||||
|
:icon="secondaryActionProps.icon"
|
||||||
|
:color="secondaryActionProps.color"
|
||||||
|
:label="secondaryActionProps.label"
|
||||||
|
:loading="secondaryActionProps.loading"
|
||||||
|
:disable="secondaryActionProps.disable"
|
||||||
|
@click="handleSecondaryAction"
|
||||||
|
v-close-popup="secondaryActionProps.closePopup"
|
||||||
|
class="q-mr-sm"
|
||||||
|
></q-btn>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="hasAction"
|
||||||
|
:icon="actionProps.icon"
|
||||||
|
:color="actionProps.color"
|
||||||
|
:label="actionProps.label"
|
||||||
|
:loading="actionProps.loading"
|
||||||
|
:disable="actionProps.disable"
|
||||||
|
@click="handleAction"
|
||||||
|
v-close-popup="actionProps.closePopup"
|
||||||
|
></q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
</template>
|
||||||
@@ -394,159 +394,135 @@
|
|||||||
></span>
|
></span>
|
||||||
</i>
|
</i>
|
||||||
</q-td>
|
</q-td>
|
||||||
<q-dialog v-model="props.expand" :props="props" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-sm q-pt-xl lnbits__dialog-card">
|
:show="props.expand"
|
||||||
<q-card-section>
|
:cancel-label="$t('close')"
|
||||||
<q-list bordered separator>
|
:action="{
|
||||||
<q-expansion-item
|
label: $t('check_payment'),
|
||||||
expand-separator
|
icon: 'refresh'
|
||||||
:default-opened="!(props.row.isIn && props.row.isPending)"
|
}"
|
||||||
>
|
@update:show="props.expand = $event"
|
||||||
<template v-slot:header>
|
@action="checkPayment(props.row.payment_hash)"
|
||||||
<q-item-section avatar>
|
>
|
||||||
<q-icon
|
<q-list bordered separator>
|
||||||
:color="
|
<q-expansion-item
|
||||||
props.row.isPaid && props.row.isIn
|
expand-separator
|
||||||
? 'green'
|
:default-opened="!(props.row.isIn && props.row.isPending)"
|
||||||
: props.row.isPaid && props.row.isOut
|
>
|
||||||
? 'pink'
|
<template v-slot:header>
|
||||||
: props.row.isFailed
|
<q-item-section avatar>
|
||||||
? 'yellow'
|
<q-icon
|
||||||
: 'grey'
|
:color="
|
||||||
"
|
props.row.isPaid && props.row.isIn
|
||||||
:name="
|
? 'green'
|
||||||
props.row.isPaid && props.row.isIn
|
: props.row.isPaid && props.row.isOut
|
||||||
? 'call_received'
|
? 'pink'
|
||||||
: props.row.isPaid && props.row.isOut
|
: props.row.isFailed
|
||||||
? 'call_made'
|
? 'yellow'
|
||||||
: props.row.isFailed
|
: 'grey'
|
||||||
? 'warning'
|
"
|
||||||
: 'settings_ethernet'
|
:name="
|
||||||
"
|
props.row.isPaid && props.row.isIn
|
||||||
/>
|
? 'call_received'
|
||||||
</q-item-section>
|
: props.row.isPaid && props.row.isOut
|
||||||
|
? 'call_made'
|
||||||
|
: props.row.isFailed
|
||||||
|
? 'warning'
|
||||||
|
: 'settings_ethernet'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label
|
<q-item-label
|
||||||
v-text="
|
v-text="
|
||||||
props.row.isIn && props.row.isPending
|
props.row.isIn && props.row.isPending
|
||||||
? $t('invoice_waiting')
|
? $t('invoice_waiting')
|
||||||
: props.row.isOut && props.row.isPending
|
: props.row.isOut && props.row.isPending
|
||||||
? $t('outgoing_payment_pending')
|
? $t('outgoing_payment_pending')
|
||||||
: props.row.isPaid && props.row.isIn
|
: props.row.isPaid && props.row.isIn
|
||||||
? $t('payment_received')
|
? $t('payment_received')
|
||||||
: props.row.isPaid && props.row.isOut
|
: props.row.isPaid && props.row.isOut
|
||||||
? $t('payment_sent')
|
? $t('payment_sent')
|
||||||
: props.row.isFailed
|
: props.row.isFailed
|
||||||
? $t('payment_failed')
|
? $t('payment_failed')
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
></q-item-label>
|
></q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section v-if="props.row.tag" side>
|
<q-item-section v-if="props.row.tag" side>
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="props.row.extra && !!props.row.extra.tag"
|
v-if="props.row.extra && !!props.row.extra.tag"
|
||||||
color="yellow"
|
color="yellow"
|
||||||
text-color="black"
|
text-color="black"
|
||||||
>
|
>
|
||||||
#<span v-text="props.row.tag"></span>
|
#<span v-text="props.row.tag"></span>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</template>
|
</template>
|
||||||
<q-separator></q-separator>
|
<q-separator></q-separator>
|
||||||
<lnbits-payment-details
|
<lnbits-payment-details
|
||||||
:payment="props.row"
|
:payment="props.row"
|
||||||
></lnbits-payment-details>
|
></lnbits-payment-details>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="props.row.isIn && props.row.isPending && props.row.bolt11"
|
||||||
props.row.isIn && props.row.isPending && props.row.bolt11
|
>
|
||||||
"
|
<div v-if="props.row.extra.fiat_payment_request">
|
||||||
>
|
<lnbits-qrcode
|
||||||
<div v-if="props.row.extra.fiat_payment_request">
|
:value="props.row.extra.fiat_payment_request"
|
||||||
<lnbits-qrcode
|
:href="props.row.extra.fiat_payment_request"
|
||||||
:value="props.row.extra.fiat_payment_request"
|
:show-buttons="false"
|
||||||
:href="props.row.extra.fiat_payment_request"
|
></lnbits-qrcode>
|
||||||
:show-buttons="false"
|
</div>
|
||||||
></lnbits-qrcode>
|
<div v-else>
|
||||||
</div>
|
<lnbits-qrcode
|
||||||
<div v-else>
|
:value="'lightning:' + props.row.bolt11.toUpperCase()"
|
||||||
<lnbits-qrcode
|
:href="'lightning:' + props.row.bolt11"
|
||||||
:value="'lightning:' + props.row.bolt11.toUpperCase()"
|
></lnbits-qrcode>
|
||||||
:href="'lightning:' + props.row.bolt11"
|
</div>
|
||||||
></lnbits-qrcode>
|
</div>
|
||||||
</div>
|
</lnbits-dialog>
|
||||||
</div>
|
<lnbits-dialog
|
||||||
<div class="row q-mt-md">
|
:show="hodlInvoice.show"
|
||||||
<q-btn
|
:cancel-label="$t('close')"
|
||||||
outline
|
:action="{
|
||||||
color="grey"
|
label: $t('cancel_invoice'),
|
||||||
@click="checkPayment(props.row.payment_hash)"
|
color: 'grey',
|
||||||
icon="refresh"
|
closePopup: true
|
||||||
:label="$t('payment_check')"
|
}"
|
||||||
></q-btn>
|
:secondary-action="{
|
||||||
<q-btn
|
label: $t('settle_invoice'),
|
||||||
v-close-popup
|
color: 'grey',
|
||||||
flat
|
closePopup: true
|
||||||
color="grey"
|
}"
|
||||||
class="q-ml-auto"
|
@update:show="hodlInvoice.show = $event"
|
||||||
:label="$t('close')"
|
@action="cancelHoldInvoice(hodlInvoice.payment.payment_hash)"
|
||||||
></q-btn>
|
@secondary-action="settleHoldInvoice(hodlInvoice.preimage)"
|
||||||
</div>
|
>
|
||||||
</q-card-section>
|
<q-card-section>
|
||||||
</q-card>
|
<q-item-label class="text-h6">
|
||||||
</q-dialog>
|
<span v-text="$t('hold_invoice')"></span>
|
||||||
<q-dialog v-model="hodlInvoice.show" position="top">
|
</q-item-label>
|
||||||
<q-card class="q-pa-sm q-pt-xl lnbits__dialog-card">
|
<q-item-label class="text-subtitle2">
|
||||||
<q-card-section>
|
<span v-text="$t('hold_invoice_description')"></span>
|
||||||
<q-item-label class="text-h6">
|
</q-item-label>
|
||||||
<span v-text="$t('hold_invoice')"></span>
|
</q-card-section>
|
||||||
</q-item-label>
|
<q-card-section>
|
||||||
<q-item-label class="text-subtitle2">
|
<q-input
|
||||||
<span v-text="$t('hold_invoice_description')"></span>
|
filled
|
||||||
</q-item-label>
|
:label="$t('preimage')"
|
||||||
</q-card-section>
|
:hint="$t('preimage_hint')"
|
||||||
<q-card-section>
|
v-model="hodlInvoice.preimage"
|
||||||
<q-input
|
dense
|
||||||
filled
|
autofocus
|
||||||
:label="$t('preimage')"
|
@keyup.enter="settleHoldInvoice(hodlInvoice.preimage)"
|
||||||
:hint="$t('preimage_hint')"
|
>
|
||||||
v-model="hodlInvoice.preimage"
|
</q-input>
|
||||||
dense
|
</q-card-section>
|
||||||
autofocus
|
</lnbits-dialog>
|
||||||
@keyup.enter="settleHoldInvoice(hodlInvoice.preimage)"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="row q-gutter-x-sm">
|
|
||||||
<q-btn
|
|
||||||
@click="settleHoldInvoice(hodlInvoice.preimage)"
|
|
||||||
outline
|
|
||||||
v-close-popup
|
|
||||||
color="grey"
|
|
||||||
:label="$t('settle_invoice')"
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-sm"
|
|
||||||
@click="cancelHoldInvoice(hodlInvoice.payment.payment_hash)"
|
|
||||||
:label="$t('cancel_invoice')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('close')"
|
|
||||||
></q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
<template id="lnbits-qrcode-scanner">
|
<template id="lnbits-qrcode-scanner">
|
||||||
<q-dialog v-model="showScanner" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-xl">
|
:show="showScanner"
|
||||||
<div class="text-center q-mb-lg">
|
:cancel-label="$t('cancel')"
|
||||||
<qrcode-stream
|
@update:show="showScanner = $event"
|
||||||
@detect="detect"
|
@cancel="reset"
|
||||||
@camera-on="onInitQR"
|
>
|
||||||
class="rounded-borders"
|
<div class="text-center q-mb-lg">
|
||||||
></qrcode-stream>
|
<qrcode-stream
|
||||||
</div>
|
@detect="detect"
|
||||||
<div class="row q-mt-lg">
|
@camera-on="onInitQR"
|
||||||
<q-btn
|
class="rounded-borders"
|
||||||
@click="reset"
|
></qrcode-stream>
|
||||||
flat
|
</div>
|
||||||
color="grey"
|
</lnbits-dialog>
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -8,49 +8,48 @@
|
|||||||
icon="edit"
|
icon="edit"
|
||||||
style="position: relative; left: -15px; bottom: -10px"
|
style="position: relative; left: -15px; bottom: -10px"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-dialog v-model="icon.show" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
:show="icon.show"
|
||||||
<q-form @submit="setIcon" class="q-gutter-md">
|
:action="{
|
||||||
<div class="q-gutter-sm q-pa-sm flex flex-wrap justify-center">
|
label: 'Save Icon',
|
||||||
|
disable: !icon.data.icon
|
||||||
|
}"
|
||||||
|
@update:show="icon.show = $event"
|
||||||
|
@action="setIcon"
|
||||||
|
@hide="icon.data = {}"
|
||||||
|
>
|
||||||
|
<q-form class="q-gutter-md">
|
||||||
|
<div class="q-gutter-sm q-pa-sm flex flex-wrap justify-center">
|
||||||
|
<q-btn
|
||||||
|
v-for="(thisIcon, index) in icon.options"
|
||||||
|
:key="index"
|
||||||
|
@click="setSelectedIcon(thisIcon)"
|
||||||
|
round
|
||||||
|
text-color="black"
|
||||||
|
:color="
|
||||||
|
icon.data.icon === thisIcon
|
||||||
|
? icon.data.color || 'primary'
|
||||||
|
: 'grey-5'
|
||||||
|
"
|
||||||
|
size="md"
|
||||||
|
:icon="thisIcon"
|
||||||
|
class="q-mb-sm"
|
||||||
|
></q-btn>
|
||||||
|
</div>
|
||||||
|
<div class="q-pa-sm flex justify-between items-center">
|
||||||
|
<div class="flex q-pl-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-for="(thisIcon, index) in icon.options"
|
v-for="(color, index) in icon.colorOptions"
|
||||||
:key="index"
|
:key="'color-' + index"
|
||||||
@click="setSelectedIcon(thisIcon)"
|
@click="setSelectedColor(color)"
|
||||||
round
|
round
|
||||||
text-color="black"
|
:color="color"
|
||||||
:color="
|
size="xs"
|
||||||
icon.data.icon === thisIcon
|
style="width: 24px; height: 24px; min-width: 24px; padding: 0"
|
||||||
? icon.data.color || 'primary'
|
class="q-mr-xs"
|
||||||
: 'grey-5'
|
|
||||||
"
|
|
||||||
size="md"
|
|
||||||
:icon="thisIcon"
|
|
||||||
class="q-mb-sm"
|
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-pa-sm flex justify-between items-center">
|
</div>
|
||||||
<div class="flex q-pl-lg">
|
</q-form>
|
||||||
<q-btn
|
</lnbits-dialog>
|
||||||
v-for="(color, index) in icon.colorOptions"
|
|
||||||
:key="'color-' + index"
|
|
||||||
@click="setSelectedColor(color)"
|
|
||||||
round
|
|
||||||
:color="color"
|
|
||||||
size="xs"
|
|
||||||
style="width: 24px; height: 24px; min-width: 24px; padding: 0"
|
|
||||||
class="q-mr-xs"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="!icon.data.icon"
|
|
||||||
type="submit"
|
|
||||||
>
|
|
||||||
Save Icon
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,94 +1,72 @@
|
|||||||
<template id="lnbits-wallet-new">
|
<template id="lnbits-wallet-new">
|
||||||
<q-dialog v-model="showNewWalletDialog" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-md lnbits__dialog-card">
|
:show="showNewWalletDialog"
|
||||||
<q-card-section>
|
:title="$t('add_new_wallet')"
|
||||||
<div class="text-h6">
|
:action="{
|
||||||
<span v-text="$t('add_new_wallet')"></span>
|
label: $t('add_wallet'),
|
||||||
</div>
|
closePopup: true
|
||||||
</q-card-section>
|
}"
|
||||||
<q-card-section v-if="g.user.walletInvitesCount">
|
:secondary-action="
|
||||||
<q-badge
|
isLightningShared
|
||||||
@click="g.newWalletType = 'lightning-shared'"
|
? {
|
||||||
class="cursor-pointer"
|
label: $t('reject_wallet'),
|
||||||
>
|
color: 'negative',
|
||||||
<span
|
closePopup: true
|
||||||
v-text="
|
}
|
||||||
'You have ' +
|
: null
|
||||||
g.user.walletInvitesCount +
|
"
|
||||||
' wallet invitation' +
|
@update:show="showNewWalletDialog = $event"
|
||||||
(g.user.walletInvitesCount > 1 ? 's' : '')
|
@action="submitAddWallet"
|
||||||
"
|
@secondary-action="submitRejectWalletInvitation"
|
||||||
></span>
|
>
|
||||||
</q-badge>
|
<q-card-section v-if="g.user.walletInvitesCount">
|
||||||
</q-card-section>
|
<q-badge
|
||||||
|
@click="g.newWalletType = 'lightning-shared'"
|
||||||
|
class="cursor-pointer"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-text="
|
||||||
|
'You have ' +
|
||||||
|
g.user.walletInvitesCount +
|
||||||
|
' wallet invitation' +
|
||||||
|
(g.user.walletInvitesCount > 1 ? 's' : '')
|
||||||
|
"
|
||||||
|
></span>
|
||||||
|
</q-badge>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="q-pt-none">
|
<q-card-section class="q-pt-none">
|
||||||
<q-select
|
<q-select
|
||||||
v-if="walletTypes.length > 1"
|
v-if="walletTypes.length > 1"
|
||||||
:options="walletTypes"
|
:options="walletTypes"
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
:label="$t('wallet_type')"
|
:label="$t('wallet_type')"
|
||||||
v-model="g.newWalletType"
|
v-model="g.newWalletType"
|
||||||
dense
|
dense
|
||||||
></q-select>
|
></q-select>
|
||||||
<q-input
|
<q-input
|
||||||
v-if="isLightning"
|
v-if="isLightning"
|
||||||
dense
|
dense
|
||||||
v-model="wallet.name"
|
v-model="wallet.name"
|
||||||
:label="$t('wallet_name')"
|
:label="$t('wallet_name')"
|
||||||
autofocus
|
autofocus
|
||||||
@keyup.enter="submitAddWallet()"
|
@keyup.enter="submitAddWallet()"
|
||||||
class="q-mt-md"
|
class="q-mt-md"
|
||||||
></q-input>
|
></q-input>
|
||||||
<q-select
|
<q-select
|
||||||
v-if="isLightningShared"
|
v-if="isLightningShared"
|
||||||
v-model="wallet.sharedWalletId"
|
v-model="wallet.sharedWalletId"
|
||||||
:label="$t('shared_wallet_id')"
|
:label="$t('shared_wallet_id')"
|
||||||
emit-value
|
emit-value
|
||||||
map-options
|
map-options
|
||||||
dense
|
dense
|
||||||
:options="inviteWalletOptions"
|
:options="inviteWalletOptions"
|
||||||
class="q-mt-md"
|
class="q-mt-md"
|
||||||
></q-select>
|
></q-select>
|
||||||
<div v-if="isLightningShared" class="q-mt-md">
|
<div v-if="isLightningShared" class="q-mt-md">
|
||||||
<span v-text="$t('shared_wallet_desc')" class="q-mt-lg"></span>
|
<span v-text="$t('shared_wallet_desc')" class="q-mt-lg"></span>
|
||||||
</div>
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
</lnbits-dialog>
|
||||||
<q-card-actions class="text-primary">
|
|
||||||
<div class="row full-width">
|
|
||||||
<div class="col-md-4">
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
:label="$t('cancel')"
|
|
||||||
class="float-left"
|
|
||||||
v-close-popup
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<q-btn
|
|
||||||
v-if="isLightningShared"
|
|
||||||
:disabled="!wallet.sharedWalletId"
|
|
||||||
flat
|
|
||||||
:label="$t('reject_wallet')"
|
|
||||||
v-close-popup
|
|
||||||
color="negative"
|
|
||||||
@click="submitRejectWalletInvitation()"
|
|
||||||
></q-btn>
|
|
||||||
<span v-else></span>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-4">
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
:label="$t('add_wallet')"
|
|
||||||
v-close-popup
|
|
||||||
@click="submitAddWallet()"
|
|
||||||
class="float-right"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+161
-200
@@ -1231,213 +1231,174 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-dialog v-model="apiAcl.showPasswordDialog" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
:show="apiAcl.showPasswordDialog"
|
||||||
<strong>User Password</strong>
|
title="Password Required"
|
||||||
<div class="row q-mt-md q-col-gutter-md">
|
:action="{
|
||||||
<div class="col-12">
|
label: $t('ok')
|
||||||
<q-input
|
}"
|
||||||
v-model="apiAcl.password"
|
@update:show="apiAcl.showPasswordDialog = $event"
|
||||||
type="password"
|
@action="runPasswordGuardedFunction()"
|
||||||
dense
|
>
|
||||||
filled
|
<div class="row q-col-gutter-md">
|
||||||
label="Password"
|
<div class="col-12">
|
||||||
hint="User password is required for this action."
|
<q-input
|
||||||
>
|
v-model="apiAcl.password"
|
||||||
</q-input>
|
type="password"
|
||||||
</div>
|
dense
|
||||||
|
filled
|
||||||
|
label="Password"
|
||||||
|
hint="User password is required for this action."
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
</div>
|
||||||
<q-btn
|
</lnbits-dialog>
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
@click="runPasswordGuardedFunction()"
|
|
||||||
:label="$t('ok')"
|
|
||||||
color="primary"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="apiAcl.showNewAclDialog" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
:show="apiAcl.showNewAclDialog"
|
||||||
<strong>New API Access Control List</strong>
|
title="New API Access Control List"
|
||||||
<div class="row q-mt-md q-col-gutter-md">
|
:action="{
|
||||||
<div class="col-12">
|
label: 'Create'
|
||||||
<q-input v-model="apiAcl.newAclName" dense filled label="ACL Name">
|
}"
|
||||||
</q-input>
|
:cancel-label="$t('close')"
|
||||||
</div>
|
@update:show="apiAcl.showNewAclDialog = $event"
|
||||||
|
@action="addApiACL()"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input v-model="apiAcl.newAclName" dense filled label="ACL Name">
|
||||||
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
</div>
|
||||||
<q-btn @click="addApiACL()" label="Create" color="primary"></q-btn>
|
</lnbits-dialog>
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('close')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
<q-dialog v-model="apiAcl.showNewTokenDialog" position="top">
|
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
|
||||||
<strong>New API Token</strong>
|
|
||||||
<div class="row q-col-gutter-md q-mt-md">
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="apiAcl.newTokenName"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
label="Token Name"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="apiAcl.newTokenExpiry"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
label="Expiration"
|
|
||||||
hit="Expiration time in minutes (default xxx)"
|
|
||||||
>
|
|
||||||
<template v-slot:prepend>
|
|
||||||
<q-icon name="event" class="cursor-pointer">
|
|
||||||
<q-popup-proxy
|
|
||||||
cover
|
|
||||||
transition-show="scale"
|
|
||||||
transition-hide="scale"
|
|
||||||
>
|
|
||||||
<q-date
|
|
||||||
v-model="apiAcl.newTokenExpiry"
|
|
||||||
mask="YYYY-MM-DD HH:mm"
|
|
||||||
>
|
|
||||||
<div class="row items-center justify-end">
|
|
||||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
|
||||||
</div>
|
|
||||||
</q-date>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:append>
|
<lnbits-dialog
|
||||||
<q-icon name="access_time" class="cursor-pointer">
|
:show="apiAcl.showNewTokenDialog"
|
||||||
<q-popup-proxy
|
title="New API Token"
|
||||||
cover
|
:action="{
|
||||||
transition-show="scale"
|
label: 'Create'
|
||||||
transition-hide="scale"
|
}"
|
||||||
|
:cancel-label="$t('close')"
|
||||||
|
@update:show="apiAcl.showNewTokenDialog = $event"
|
||||||
|
@action="generateApiToken()"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input v-model="apiAcl.newTokenName" dense filled label="Token Name">
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input
|
||||||
|
v-model="apiAcl.newTokenExpiry"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
label="Expiration"
|
||||||
|
hit="Expiration time in minutes (default xxx)"
|
||||||
|
>
|
||||||
|
<template v-slot:prepend>
|
||||||
|
<q-icon name="event" class="cursor-pointer">
|
||||||
|
<q-popup-proxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<q-date v-model="apiAcl.newTokenExpiry" mask="YYYY-MM-DD HH:mm">
|
||||||
|
<div class="row items-center justify-end">
|
||||||
|
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||||
|
</div>
|
||||||
|
</q-date>
|
||||||
|
</q-popup-proxy>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="access_time" class="cursor-pointer">
|
||||||
|
<q-popup-proxy
|
||||||
|
cover
|
||||||
|
transition-show="scale"
|
||||||
|
transition-hide="scale"
|
||||||
|
>
|
||||||
|
<q-time
|
||||||
|
v-model="apiAcl.newTokenExpiry"
|
||||||
|
mask="YYYY-MM-DD HH:mm"
|
||||||
|
format24h
|
||||||
>
|
>
|
||||||
<q-time
|
<div class="row items-center justify-end">
|
||||||
v-model="apiAcl.newTokenExpiry"
|
<q-btn v-close-popup label="Close" color="primary" flat />
|
||||||
mask="YYYY-MM-DD HH:mm"
|
</div>
|
||||||
format24h
|
</q-time>
|
||||||
>
|
</q-popup-proxy>
|
||||||
<div class="row items-center justify-end">
|
</q-icon>
|
||||||
<q-btn v-close-popup label="Close" color="primary" flat />
|
</template>
|
||||||
</div>
|
</q-input>
|
||||||
</q-time>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
</div>
|
||||||
<q-btn
|
</lnbits-dialog>
|
||||||
@click="generateApiToken()"
|
|
||||||
label="Create"
|
<lnbits-dialog
|
||||||
color="primary"
|
:show="labelsDialog.show"
|
||||||
></q-btn>
|
:title="$t('label')"
|
||||||
<q-btn
|
:action="{
|
||||||
v-close-popup
|
label: g.user.extra.labels.some(
|
||||||
flat
|
label => label.name === labelsDialog.data.name
|
||||||
color="grey"
|
)
|
||||||
class="q-ml-auto"
|
? $t('update_label')
|
||||||
v-text="$t('close')"
|
: $t('add_label'),
|
||||||
></q-btn>
|
disable: !labelsDialog.data.name || !labelsDialog.data.color
|
||||||
|
}"
|
||||||
|
:cancel-label="$t('cancel')"
|
||||||
|
@update:show="labelsDialog.show = $event"
|
||||||
|
@action="
|
||||||
|
g.user.extra.labels.some(label => label.name === labelsDialog.data.name)
|
||||||
|
? updateUserLabel()
|
||||||
|
: addUserLabel()
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="row q-col-gutter-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-input
|
||||||
|
v-model="labelsDialog.data.name"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:label="$t('name')"
|
||||||
|
>
|
||||||
|
</q-input>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
<div class="col-12">
|
||||||
</q-dialog>
|
<q-input
|
||||||
<q-dialog v-model="labelsDialog.show" position="top">
|
v-model="labelsDialog.data.description"
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
dense
|
||||||
<strong v-text="$t('label')"></strong>
|
filled
|
||||||
<div class="row q-mt-md q-col-gutter-md">
|
type="textarea"
|
||||||
<div class="col-12">
|
rows="3"
|
||||||
<q-input
|
:label="$t('description')"
|
||||||
v-model="labelsDialog.data.name"
|
>
|
||||||
dense
|
</q-input>
|
||||||
filled
|
|
||||||
:label="$t('name')"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="labelsDialog.data.description"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
type="textarea"
|
|
||||||
rows="3"
|
|
||||||
:label="$t('description')"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="labelsDialog.data.color"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
class="my-input"
|
|
||||||
>
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="colorize" class="cursor-pointer">
|
|
||||||
<q-popup-proxy
|
|
||||||
cover
|
|
||||||
transition-show="scale"
|
|
||||||
transition-hide="scale"
|
|
||||||
>
|
|
||||||
<q-color
|
|
||||||
v-model="labelsDialog.data.color"
|
|
||||||
default-view="palette"
|
|
||||||
/>
|
|
||||||
</q-popup-proxy>
|
|
||||||
</q-icon>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="col-12">
|
||||||
<q-btn
|
<q-input
|
||||||
v-close-popup
|
v-model="labelsDialog.data.color"
|
||||||
flat
|
filled
|
||||||
color="grey"
|
dense
|
||||||
class="q-ml-auto"
|
class="my-input"
|
||||||
v-text="$t('cancel')"
|
>
|
||||||
></q-btn>
|
<template v-slot:append>
|
||||||
<q-btn
|
<q-icon name="colorize" class="cursor-pointer">
|
||||||
v-if="
|
<q-popup-proxy
|
||||||
g.user.extra.labels.some(
|
cover
|
||||||
label => label.name === labelsDialog.data.name
|
transition-show="scale"
|
||||||
)
|
transition-hide="scale"
|
||||||
"
|
>
|
||||||
@click="updateUserLabel()"
|
<q-color
|
||||||
:disable="!labelsDialog.data.name || !labelsDialog.data.color"
|
v-model="labelsDialog.data.color"
|
||||||
:label="$t('update_label')"
|
default-view="palette"
|
||||||
color="primary"
|
/>
|
||||||
></q-btn>
|
</q-popup-proxy>
|
||||||
<q-btn
|
</q-icon>
|
||||||
v-else
|
</template>
|
||||||
@click="addUserLabel()"
|
</q-input>
|
||||||
:disable="!labelsDialog.data.name || !labelsDialog.data.color"
|
|
||||||
:label="$t('add_label')"
|
|
||||||
color="primary"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</div>
|
||||||
</q-dialog>
|
</lnbits-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -170,33 +170,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-dialog v-model="auditDetailsDialog.show" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
:show="auditDetailsDialog.show"
|
||||||
<strong v-text="$t('http_request_details')"></strong>
|
:title="$t('http_request_details')"
|
||||||
<q-input
|
:action="{
|
||||||
filled
|
label: $t('copy'),
|
||||||
dense
|
icon: 'content_copy'
|
||||||
v-model.trim="auditDetailsDialog.data"
|
}"
|
||||||
type="textarea"
|
:cancel-label="$t('close')"
|
||||||
rows="25"
|
@update:show="auditDetailsDialog.show = $event"
|
||||||
></q-input>
|
@action="utils.copyText(auditDetailsDialog.data)"
|
||||||
|
>
|
||||||
<div class="row q-mt-lg">
|
<q-input
|
||||||
<q-btn
|
filled
|
||||||
@click="utils.copyText(auditDetailsDialog.data)"
|
dense
|
||||||
icon="content_copy"
|
v-model.trim="auditDetailsDialog.data"
|
||||||
color="grey"
|
type="textarea"
|
||||||
flat
|
rows="25"
|
||||||
v-text="$t('copy')"
|
></q-input>
|
||||||
></q-btn>
|
</lnbits-dialog>
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('close')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -366,72 +366,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<q-dialog v-model="showUninstallDialog" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg">
|
:show="showUninstallDialog"
|
||||||
<h6 class="q-my-md text-primary" v-text="$t('warning')"></h6>
|
:title="$t('warning')"
|
||||||
<p>
|
:action="{
|
||||||
<span v-text="$t('extension_uninstall_warning')"></span><br />
|
label: $t('uninstall_confirm')
|
||||||
<span v-text="$t('confirm_continue')"></span>
|
}"
|
||||||
</p>
|
:cancel-label="$t('cancel')"
|
||||||
|
@update:show="showUninstallDialog = $event"
|
||||||
|
@action="uninstallExtension()"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
<span v-text="$t('extension_uninstall_warning')"></span><br />
|
||||||
|
<span v-text="$t('confirm_continue')"></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-checkbox
|
<q-checkbox
|
||||||
v-model="uninstallAndDropDb"
|
v-model="uninstallAndDropDb"
|
||||||
value="false"
|
value="false"
|
||||||
label="Cleanup database tables"
|
label="Cleanup database tables"
|
||||||
>
|
>
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
||||||
><span v-text="$t('extension_db_drop_info')"></span>
|
><span v-text="$t('extension_db_drop_info')"></span>
|
||||||
</q-tooltip>
|
</q-tooltip>
|
||||||
</q-checkbox>
|
</q-checkbox>
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
</lnbits-dialog>
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="uninstallExtension()"
|
|
||||||
v-text="$t('uninstall_confirm')"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="showDropDbDialog" position="top">
|
<lnbits-dialog
|
||||||
<q-card v-if="selectedExtension" class="q-pa-lg">
|
v-if="selectedExtension"
|
||||||
<h6 class="q-my-md text-primary" v-text="$t('warning')"></h6>
|
:show="showDropDbDialog"
|
||||||
<p><span v-text="$t('extension_db_drop_warning')"></span><br /></p>
|
:title="$t('warning')"
|
||||||
<q-input
|
:action="{
|
||||||
v-model="dropDbExtensionId"
|
label: $t('confirm'),
|
||||||
:label="selectedExtension.id"
|
color: 'red',
|
||||||
></q-input>
|
disable: dropDbExtensionId !== selectedExtension.id
|
||||||
<br />
|
}"
|
||||||
<p v-text="$t('confirm_continue')"></p>
|
:cancel-label="$t('cancel')"
|
||||||
|
@update:show="showDropDbDialog = $event"
|
||||||
|
@action="dropExtensionDb()"
|
||||||
|
>
|
||||||
|
<p><span v-text="$t('extension_db_drop_warning')"></span><br /></p>
|
||||||
|
<q-input
|
||||||
|
v-model="dropDbExtensionId"
|
||||||
|
:label="selectedExtension.id"
|
||||||
|
></q-input>
|
||||||
|
<br />
|
||||||
|
<p v-text="$t('confirm_continue')"></p>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
:disable="dropDbExtensionId !== selectedExtension.id"
|
:disable="dropDbExtensionId !== selectedExtension.id"
|
||||||
outline
|
outline
|
||||||
color="red"
|
color="red"
|
||||||
@click="dropExtensionDb()"
|
@click="dropExtensionDb()"
|
||||||
v-text="$t('confirm')"
|
v-text="$t('confirm')"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-close-popup
|
v-close-popup
|
||||||
flat
|
flat
|
||||||
color="grey"
|
color="grey"
|
||||||
class="q-ml-auto"
|
class="q-ml-auto"
|
||||||
v-text="$t('cancel')"
|
v-text="$t('cancel')"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</lnbits-dialog>
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="showManageExtensionDialog" position="top">
|
<q-dialog v-model="showManageExtensionDialog" position="top">
|
||||||
<q-card v-if="selectedRelease" class="q-pa-lg lnbits__dialog-card">
|
<q-card v-if="selectedRelease" class="q-pa-lg lnbits__dialog-card">
|
||||||
@@ -1244,76 +1244,61 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
<q-dialog v-model="paymentDialog.show" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md lnbits__dialog-card">
|
:show="paymentDialog.show"
|
||||||
<q-card-section>
|
:cancel-label="$t('close')"
|
||||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-xl">
|
@update:show="paymentDialog.show = $event"
|
||||||
<lnbits-qrcode
|
>
|
||||||
:value="paymentDialog.invoice"
|
<q-responsive :ratio="1" class="q-mx-xl q-mb-xl">
|
||||||
:options="{width: 800}"
|
<lnbits-qrcode
|
||||||
class="rounded-borders"
|
:value="paymentDialog.invoice"
|
||||||
></lnbits-qrcode>
|
:options="{width: 800}"
|
||||||
</q-responsive>
|
class="rounded-borders"
|
||||||
</q-card-section>
|
></lnbits-qrcode>
|
||||||
<q-card-actions align="between">
|
</q-responsive>
|
||||||
<q-btn v-close-popup flat color="grey" :label="$t('close')"></q-btn>
|
</lnbits-dialog>
|
||||||
</q-card-actions>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
<q-dialog v-model="showUpdateAllDialog" position="top">
|
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<h6 class="q-my-md" v-text="$t('update')"></h6>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="updatableExtensions?.length > 1" class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="selectAllUpdatableExtensionss()"
|
|
||||||
v-text="$t('select_all')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-virtual-scroll :items="updatableExtensions" style="max-height: 400px">
|
<lnbits-dialog
|
||||||
<template v-slot="{item, index}">
|
:show="showUpdateAllDialog"
|
||||||
<div class="row">
|
:title="$t('update')"
|
||||||
<div class="q-col">
|
:action="{
|
||||||
<q-checkbox
|
label: $t('update')
|
||||||
v-model="item.selectedForUpdate"
|
}"
|
||||||
:disable="item.isUpgraded"
|
:cancel-label="$t('cancel')"
|
||||||
value="false"
|
@update:show="showUpdateAllDialog = $event"
|
||||||
:label="item.name + ` (v${item.latestRelease?.version})`"
|
@action="updateSelectedExtensions()"
|
||||||
>
|
>
|
||||||
<q-spinner-bars
|
<div v-if="updatableExtensions?.length > 1" class="row">
|
||||||
v-if="item.inProgress"
|
<div class="col-12 q-mb-md">
|
||||||
color="primary"
|
|
||||||
size="1.5em"
|
|
||||||
class="q-ml-md"
|
|
||||||
></q-spinner-bars>
|
|
||||||
</q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-virtual-scroll>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="updateSelectedExtensions()"
|
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="grey"
|
||||||
v-text="$t('update')"
|
@click="selectAllUpdatableExtensionss()"
|
||||||
></q-btn>
|
v-text="$t('select_all')"
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
v-text="$t('cancel')"
|
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</div>
|
||||||
</q-dialog>
|
|
||||||
|
<q-virtual-scroll :items="updatableExtensions" style="max-height: 400px">
|
||||||
|
<template v-slot="{item, index}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="q-col">
|
||||||
|
<q-checkbox
|
||||||
|
v-model="item.selectedForUpdate"
|
||||||
|
:disable="item.isUpgraded"
|
||||||
|
value="false"
|
||||||
|
:label="item.name + ` (v${item.latestRelease?.version})`"
|
||||||
|
>
|
||||||
|
<q-spinner-bars
|
||||||
|
v-if="item.inProgress"
|
||||||
|
color="primary"
|
||||||
|
size="1.5em"
|
||||||
|
class="q-ml-md"
|
||||||
|
></q-spinner-bars>
|
||||||
|
</q-checkbox>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
</lnbits-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
+117
-154
@@ -103,164 +103,127 @@
|
|||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
<q-tab-panel name="channels">
|
<q-tab-panel name="channels">
|
||||||
<q-dialog v-model="connectPeerDialog.show" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
:show="connectPeerDialog.show"
|
||||||
<q-form class="q-gutter-md">
|
:action="{
|
||||||
<q-input
|
label: $t('connect')
|
||||||
dense
|
}"
|
||||||
type="text"
|
:cancel-label="$t('cancel')"
|
||||||
filled
|
@update:show="connectPeerDialog.show = $event"
|
||||||
v-model="connectPeerDialog.data.uri"
|
@action="connectPeer"
|
||||||
label="Node URI"
|
>
|
||||||
hint="pubkey@host:port"
|
<q-form class="q-gutter-md">
|
||||||
></q-input>
|
<q-input
|
||||||
|
dense
|
||||||
|
type="text"
|
||||||
|
filled
|
||||||
|
v-model="connectPeerDialog.data.uri"
|
||||||
|
label="Node URI"
|
||||||
|
hint="pubkey@host:port"
|
||||||
|
></q-input>
|
||||||
|
</q-form>
|
||||||
|
</lnbits-dialog>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<lnbits-dialog
|
||||||
<q-btn
|
:show="setFeeDialog.show"
|
||||||
:label="$t('connect')"
|
title="Set Channel Fee"
|
||||||
color="primary"
|
:action="{
|
||||||
@click="connectPeer"
|
label: $t('set')
|
||||||
></q-btn>
|
}"
|
||||||
<q-btn
|
:cancel-label="$t('cancel')"
|
||||||
v-close-popup
|
@update:show="setFeeDialog.show = $event"
|
||||||
flat
|
@action="setChannelFee(setFeeDialog.channel_id)"
|
||||||
color="grey"
|
>
|
||||||
class="q-ml-auto"
|
<p class="text-caption" v-text="setFeeDialog.channel_id"></p>
|
||||||
:label="$t('cancel')"
|
<q-separator></q-separator>
|
||||||
></q-btn>
|
<q-form class="q-gutter-md">
|
||||||
</div>
|
<q-input
|
||||||
</q-form>
|
dense
|
||||||
</q-card>
|
type="number"
|
||||||
</q-dialog>
|
filled
|
||||||
|
v-model.number="setFeeDialog.data.fee_ppm"
|
||||||
|
label="Fee Rate PPM"
|
||||||
|
></q-input>
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
type="number"
|
||||||
|
filled
|
||||||
|
v-model.number="setFeeDialog.data.fee_base_msat"
|
||||||
|
label="Fee Base msat"
|
||||||
|
></q-input>
|
||||||
|
</q-form>
|
||||||
|
</lnbits-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="setFeeDialog.show">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
:show="openChannelDialog.show"
|
||||||
<label class="text-h6">Set Channel Fee</label>
|
:action="{
|
||||||
<p class="text-caption" v-text="setFeeDialog.channel_id"></p>
|
label: $t('open')
|
||||||
<q-separator></q-separator>
|
}"
|
||||||
<q-form class="q-gutter-md">
|
:cancel-label="$t('cancel')"
|
||||||
<q-input
|
@update:show="openChannelDialog.show = $event"
|
||||||
dense
|
@action="openChannel"
|
||||||
type="number"
|
>
|
||||||
filled
|
<q-form class="q-gutter-md">
|
||||||
v-model.number="setFeeDialog.data.fee_ppm"
|
<q-input
|
||||||
label="Fee Rate PPM"
|
dense
|
||||||
></q-input>
|
type="text"
|
||||||
<q-input
|
filled
|
||||||
dense
|
v-model="openChannelDialog.data.peer_id"
|
||||||
type="number"
|
label="Peer ID"
|
||||||
filled
|
></q-input>
|
||||||
v-model.number="setFeeDialog.data.fee_base_msat"
|
<q-input
|
||||||
label="Fee Base msat"
|
dense
|
||||||
></q-input>
|
type="number"
|
||||||
|
filled
|
||||||
|
v-model.number="openChannelDialog.data.funding_amount"
|
||||||
|
label="Funding Amount"
|
||||||
|
></q-input>
|
||||||
|
<q-expansion-item icon="warning" label="Advanced">
|
||||||
|
<q-card>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="column q-gutter-md">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
type="number"
|
||||||
|
filled
|
||||||
|
v-model.number="openChannelDialog.data.push_amount"
|
||||||
|
label="Push Amount"
|
||||||
|
hint="This gifts sats to the other side!"
|
||||||
|
></q-input>
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
<q-input
|
||||||
<q-btn
|
dense
|
||||||
:label="$t('set')"
|
type="number"
|
||||||
color="primary"
|
filled
|
||||||
@click="setChannelFee(setFeeDialog.channel_id)"
|
v-model.number="openChannelDialog.data.fee_rate"
|
||||||
></q-btn>
|
label="Fee Rate"
|
||||||
<q-btn
|
></q-input>
|
||||||
v-close-popup
|
</div>
|
||||||
flat
|
</q-card-section>
|
||||||
color="grey"
|
</q-card>
|
||||||
class="q-ml-auto"
|
</q-expansion-item>
|
||||||
:label="$t('cancel')"
|
</q-form>
|
||||||
></q-btn>
|
</lnbits-dialog>
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="openChannelDialog.show">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
:show="closeChannelDialog.show"
|
||||||
<q-form class="q-gutter-md">
|
title="Close Channel"
|
||||||
<q-input
|
:action="{
|
||||||
dense
|
label: $t('close')
|
||||||
type="text"
|
}"
|
||||||
filled
|
:cancel-label="$t('cancel')"
|
||||||
v-model="openChannelDialog.data.peer_id"
|
@update:show="closeChannelDialog.show = $event"
|
||||||
label="Peer ID"
|
@action="closeChannel"
|
||||||
></q-input>
|
>
|
||||||
<q-input
|
<q-form class="q-gutter-md">
|
||||||
dense
|
<div>
|
||||||
type="number"
|
<q-checkbox
|
||||||
filled
|
v-model="closeChannelDialog.data.force"
|
||||||
v-model.number="openChannelDialog.data.funding_amount"
|
label="Force"
|
||||||
label="Funding Amount"
|
/>
|
||||||
></q-input>
|
</div>
|
||||||
<q-expansion-item icon="warning" label="Advanced">
|
</q-form>
|
||||||
<q-card>
|
</lnbits-dialog>
|
||||||
<q-card-section>
|
|
||||||
<div class="column q-gutter-md">
|
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
type="number"
|
|
||||||
filled
|
|
||||||
v-model.number="
|
|
||||||
openChannelDialog.data.push_amount
|
|
||||||
"
|
|
||||||
label="Push Amount"
|
|
||||||
hint="This gifts sats to the other side!"
|
|
||||||
></q-input>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
type="number"
|
|
||||||
filled
|
|
||||||
v-model.number="openChannelDialog.data.fee_rate"
|
|
||||||
label="Fee Rate"
|
|
||||||
></q-input>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-expansion-item>
|
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
:label="$t('open')"
|
|
||||||
color="primary"
|
|
||||||
@click="openChannel"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="closeChannelDialog.show" position="top">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-form class="q-gutter-md">
|
|
||||||
<div>
|
|
||||||
<q-checkbox
|
|
||||||
v-model="closeChannelDialog.data.force"
|
|
||||||
label="Force"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
:label="$t('close')"
|
|
||||||
color="primary"
|
|
||||||
@click="closeChannel"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
:label="$t('cancel')"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
<div class="row q-col-gutter-lg">
|
<div class="row q-col-gutter-lg">
|
||||||
|
|||||||
@@ -446,50 +446,44 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="activeWallet.show">
|
<div v-else-if="activeWallet.show">
|
||||||
<q-dialog v-model="createWalletDialog.show" position="top">
|
<lnbits-dialog
|
||||||
<q-card class="q-pa-md q-pt-md lnbits__dialog-card">
|
:show="createWalletDialog.show"
|
||||||
<strong>Create Wallet</strong>
|
title="Create Wallet"
|
||||||
<div class="row">
|
:action="{
|
||||||
<div class="col-12">
|
label: 'Create',
|
||||||
<div class="row q-mt-lg">
|
closePopup: true
|
||||||
<div class="col">
|
}"
|
||||||
<q-input
|
cancel-label="Cancel"
|
||||||
v-model="createWalletDialog.data.name"
|
@update:show="createWalletDialog.show = $event"
|
||||||
:label="$t('name_your_wallet')"
|
@action="createWallet"
|
||||||
filled
|
>
|
||||||
dense
|
<div class="row">
|
||||||
class="q-mb-md"
|
<div class="col-12">
|
||||||
>
|
<div class="row q-mt-lg">
|
||||||
</q-input>
|
<div class="col">
|
||||||
</div>
|
<q-input
|
||||||
</div>
|
v-model="createWalletDialog.data.name"
|
||||||
<div class="row q-mt-lg">
|
:label="$t('name_your_wallet')"
|
||||||
<div class="col">
|
filled
|
||||||
<q-select
|
dense
|
||||||
filled
|
class="q-mb-md"
|
||||||
dense
|
|
||||||
v-model="createWalletDialog.data.currency"
|
|
||||||
:options="{{ currencies | safe }}"
|
|
||||||
></q-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
@click="createWallet()"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
>Create</q-btn
|
|
||||||
>
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
|
||||||
>Cancel</q-btn
|
|
||||||
>
|
>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row q-mt-lg">
|
||||||
|
<div class="col">
|
||||||
|
<q-select
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
v-model="createWalletDialog.data.currency"
|
||||||
|
:options="g.allowedCurrencies"
|
||||||
|
></q-select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</div>
|
||||||
</q-dialog>
|
</lnbits-dialog>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
|
|||||||
Generated
+8
-7
@@ -13,7 +13,7 @@
|
|||||||
"qrcode.vue": "^3.6.0",
|
"qrcode.vue": "^3.6.0",
|
||||||
"quasar": "2.18.6",
|
"quasar": "2.18.6",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"underscore": "^1.13.7",
|
"underscore": "^1.13.8",
|
||||||
"vue": "3.5.25",
|
"vue": "3.5.25",
|
||||||
"vue-i18n": "^11.2.2",
|
"vue-i18n": "^11.2.2",
|
||||||
"vue-qrcode-reader": "^5.7.3",
|
"vue-qrcode-reader": "^5.7.3",
|
||||||
@@ -1260,9 +1260,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "5.1.4",
|
"version": "5.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
|
||||||
"integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==",
|
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -1722,9 +1722,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/underscore": {
|
"node_modules/underscore": {
|
||||||
"version": "1.13.7",
|
"version": "1.13.8",
|
||||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz",
|
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz",
|
||||||
"integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g=="
|
"integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==",
|
||||||
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.5.25",
|
"version": "3.5.25",
|
||||||
|
|||||||
+2
-1
@@ -28,7 +28,7 @@
|
|||||||
"qrcode.vue": "^3.6.0",
|
"qrcode.vue": "^3.6.0",
|
||||||
"quasar": "2.18.6",
|
"quasar": "2.18.6",
|
||||||
"showdown": "^2.1.0",
|
"showdown": "^2.1.0",
|
||||||
"underscore": "^1.13.7",
|
"underscore": "^1.13.8",
|
||||||
"vue": "3.5.25",
|
"vue": "3.5.25",
|
||||||
"vue-i18n": "^11.2.2",
|
"vue-i18n": "^11.2.2",
|
||||||
"vue-qrcode-reader": "^5.7.3",
|
"vue-qrcode-reader": "^5.7.3",
|
||||||
@@ -123,6 +123,7 @@
|
|||||||
"js/components/admin/lnbits-admin-site-customisation.js",
|
"js/components/admin/lnbits-admin-site-customisation.js",
|
||||||
"js/components/admin/lnbits-admin-assets-config.js",
|
"js/components/admin/lnbits-admin-assets-config.js",
|
||||||
"js/components/admin/lnbits-admin-audit.js",
|
"js/components/admin/lnbits-admin-audit.js",
|
||||||
|
"js/components/lnbits-dialog.js",
|
||||||
"js/components/lnbits-wallet-charts.js",
|
"js/components/lnbits-wallet-charts.js",
|
||||||
"js/components/lnbits-wallet-api-docs.js",
|
"js/components/lnbits-wallet-api-docs.js",
|
||||||
"js/components/lnbits-wallet-icon.js",
|
"js/components/lnbits-wallet-icon.js",
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ from pydantic import BaseModel
|
|||||||
from lnbits.wallets import get_funding_source, set_funding_source
|
from lnbits.wallets import get_funding_source, set_funding_source
|
||||||
|
|
||||||
|
|
||||||
class FakeError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class DbTestModel(BaseModel):
|
class DbTestModel(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import asyncio
|
|||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from pytest_mock.plugin import MockerFixture
|
||||||
|
|
||||||
from lnbits import bolt11
|
from lnbits import bolt11
|
||||||
from lnbits.core.crud import get_standalone_payment, update_payment
|
from lnbits.core.crud import get_standalone_payment, update_payment
|
||||||
@@ -18,7 +19,7 @@ from lnbits.exceptions import PaymentError
|
|||||||
from lnbits.tasks import create_task, wait_for_paid_invoices
|
from lnbits.tasks import create_task, wait_for_paid_invoices
|
||||||
from lnbits.wallets import get_funding_source
|
from lnbits.wallets import get_funding_source
|
||||||
|
|
||||||
from ..helpers import FakeError, is_fake, is_regtest
|
from ..helpers import is_fake, is_regtest
|
||||||
from .helpers import (
|
from .helpers import (
|
||||||
cancel_invoice,
|
cancel_invoice,
|
||||||
get_real_invoice,
|
get_real_invoice,
|
||||||
@@ -138,7 +139,9 @@ async def test_pay_real_invoice_mainnet(
|
|||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
||||||
async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_from):
|
async def test_create_real_invoice(
|
||||||
|
client, adminkey_headers_from, inkey_headers_from, mocker: MockerFixture
|
||||||
|
):
|
||||||
prev_balance = await get_node_balance_sats()
|
prev_balance = await get_node_balance_sats()
|
||||||
create_invoice = CreateInvoice(out=False, amount=1000, memo="test")
|
create_invoice = CreateInvoice(out=False, amount=1000, memo="test")
|
||||||
response = await client.post(
|
response = await client.post(
|
||||||
@@ -156,34 +159,32 @@ async def test_create_real_invoice(client, adminkey_headers_from, inkey_headers_
|
|||||||
payment_status = response.json()
|
payment_status = response.json()
|
||||||
assert not payment_status["paid"]
|
assert not payment_status["paid"]
|
||||||
|
|
||||||
async def on_paid(payment: Payment):
|
on_paid_mock = mocker.AsyncMock()
|
||||||
|
create_task(wait_for_paid_invoices("test_create_invoice", on_paid_mock)())
|
||||||
|
|
||||||
assert payment.payment_hash == invoice["payment_hash"]
|
|
||||||
assert payment.checking_id == invoice["checking_id"]
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
|
|
||||||
)
|
|
||||||
assert response.status_code < 300
|
|
||||||
payment_status = response.json()
|
|
||||||
assert payment_status["paid"]
|
|
||||||
|
|
||||||
await asyncio.sleep(1)
|
|
||||||
balance = await get_node_balance_sats()
|
|
||||||
fee = abs(payment_status.get("details", {}).get("fee", 0) // 1000)
|
|
||||||
assert balance - prev_balance == create_invoice.amount - fee
|
|
||||||
|
|
||||||
assert payment_status.get("preimage") is not None
|
|
||||||
|
|
||||||
# exit out of infinite loop
|
|
||||||
raise FakeError()
|
|
||||||
|
|
||||||
task = create_task(wait_for_paid_invoices("test_create_invoice", on_paid)())
|
|
||||||
pay_real_invoice(invoice["bolt11"])
|
pay_real_invoice(invoice["bolt11"])
|
||||||
|
|
||||||
# wait for the task to exit
|
await asyncio.sleep(1)
|
||||||
with pytest.raises(FakeError):
|
|
||||||
await task
|
assert on_paid_mock.call_count == 1
|
||||||
|
payment = on_paid_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
assert payment.payment_hash == invoice["payment_hash"]
|
||||||
|
assert payment.checking_id == invoice["checking_id"]
|
||||||
|
|
||||||
|
response = await client.get(
|
||||||
|
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
|
||||||
|
)
|
||||||
|
assert response.status_code < 300
|
||||||
|
payment_status = response.json()
|
||||||
|
assert payment_status["paid"]
|
||||||
|
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
balance = await get_node_balance_sats()
|
||||||
|
fee = abs(payment_status.get("details", {}).get("fee", 0) // 1000)
|
||||||
|
assert balance - prev_balance == create_invoice.amount - fee
|
||||||
|
|
||||||
|
assert payment_status.get("preimage") is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
@@ -367,7 +368,7 @@ async def test_pay_hold_invoice_check_pending_and_fail_cancel_payment_task_in_me
|
|||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
@pytest.mark.skipif(is_fake, reason="this only works in regtest")
|
||||||
async def test_receive_real_invoice_set_pending_and_check_state(
|
async def test_receive_real_invoice_set_pending_and_check_state(
|
||||||
client, adminkey_headers_from, inkey_headers_from
|
client, adminkey_headers_from, inkey_headers_from, mocker: MockerFixture
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
1. We create a real invoice
|
1. We create a real invoice
|
||||||
@@ -391,39 +392,36 @@ async def test_receive_real_invoice_set_pending_and_check_state(
|
|||||||
payment_status = response.json()
|
payment_status = response.json()
|
||||||
assert not payment_status["paid"]
|
assert not payment_status["paid"]
|
||||||
|
|
||||||
async def on_paid(payment: Payment):
|
on_paid_mock = mocker.AsyncMock()
|
||||||
|
create_task(wait_for_paid_invoices("test_create_invoice", on_paid_mock)())
|
||||||
|
|
||||||
assert payment.payment_hash == invoice["payment_hash"]
|
|
||||||
assert payment.checking_id == invoice["checking_id"]
|
|
||||||
|
|
||||||
response = await client.get(
|
|
||||||
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
|
|
||||||
)
|
|
||||||
assert response.status_code < 300
|
|
||||||
payment_status = response.json()
|
|
||||||
assert payment_status["paid"]
|
|
||||||
|
|
||||||
assert payment
|
|
||||||
|
|
||||||
# set the incoming invoice to pending
|
|
||||||
payment.status = PaymentState.PENDING
|
|
||||||
await update_payment(payment)
|
|
||||||
|
|
||||||
payment_pending = await get_standalone_payment(
|
|
||||||
invoice["payment_hash"], incoming=True
|
|
||||||
)
|
|
||||||
assert payment_pending
|
|
||||||
assert payment_pending.success is False
|
|
||||||
assert payment_pending.failed is False
|
|
||||||
|
|
||||||
# exit out of infinite loop
|
|
||||||
raise FakeError()
|
|
||||||
|
|
||||||
task = create_task(wait_for_paid_invoices("test_create_invoice", on_paid)())
|
|
||||||
pay_real_invoice(invoice["bolt11"])
|
pay_real_invoice(invoice["bolt11"])
|
||||||
|
|
||||||
with pytest.raises(FakeError):
|
await asyncio.sleep(1)
|
||||||
await task
|
|
||||||
|
assert on_paid_mock.call_count == 1
|
||||||
|
payment = on_paid_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
assert payment.payment_hash == invoice["payment_hash"]
|
||||||
|
assert payment.checking_id == invoice["checking_id"]
|
||||||
|
|
||||||
|
response = await client.get(
|
||||||
|
f'/api/v1/payments/{invoice["payment_hash"]}', headers=inkey_headers_from
|
||||||
|
)
|
||||||
|
assert response.status_code < 300
|
||||||
|
payment_status = response.json()
|
||||||
|
assert payment_status["paid"]
|
||||||
|
|
||||||
|
# set the incoming invoice to pending
|
||||||
|
payment.status = PaymentState.PENDING
|
||||||
|
await update_payment(payment)
|
||||||
|
|
||||||
|
payment_pending = await get_standalone_payment(
|
||||||
|
invoice["payment_hash"], incoming=True
|
||||||
|
)
|
||||||
|
assert payment_pending
|
||||||
|
assert payment_pending.success is False
|
||||||
|
assert payment_pending.failed is False
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ from pytest_mock.plugin import MockerFixture
|
|||||||
|
|
||||||
from lnbits.core.crud import create_wallet, get_standalone_payment, get_wallet
|
from lnbits.core.crud import create_wallet, get_standalone_payment, get_wallet
|
||||||
from lnbits.core.crud.payments import get_payment, get_payments_paginated
|
from lnbits.core.crud.payments import get_payment, get_payments_paginated
|
||||||
from lnbits.core.models import Payment, PaymentState, Wallet
|
from lnbits.core.models import PaymentState, Wallet
|
||||||
from lnbits.core.services import create_invoice, create_user_account, pay_invoice
|
from lnbits.core.services import create_invoice, create_user_account, pay_invoice
|
||||||
from lnbits.core.services.payments import update_wallet_balance
|
from lnbits.core.services.payments import update_wallet_balance
|
||||||
from lnbits.exceptions import InvoiceError, PaymentError
|
from lnbits.exceptions import InvoiceError, PaymentError
|
||||||
from lnbits.settings import Settings
|
from lnbits.settings import Settings
|
||||||
from lnbits.tasks import (
|
from lnbits.tasks import create_task, wait_for_paid_invoices
|
||||||
create_permanent_task,
|
|
||||||
internal_invoice_listener,
|
|
||||||
register_invoice_listener,
|
|
||||||
)
|
|
||||||
from lnbits.wallets.base import PaymentResponse
|
from lnbits.wallets.base import PaymentResponse
|
||||||
from lnbits.wallets.fake import FakeWallet
|
from lnbits.wallets.fake import FakeWallet
|
||||||
|
|
||||||
@@ -230,13 +226,13 @@ async def test_pay_for_extension(to_wallet: Wallet, settings: Settings):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_notification_for_internal_payment(to_wallet: Wallet):
|
async def test_notification_for_internal_payment(
|
||||||
|
to_wallet: Wallet, mocker: MockerFixture
|
||||||
|
):
|
||||||
test_name = "test_notification_for_internal_payment"
|
test_name = "test_notification_for_internal_payment"
|
||||||
|
|
||||||
create_permanent_task(internal_invoice_listener)
|
on_paid_mock = mocker.AsyncMock()
|
||||||
invoice_queue: asyncio.Queue = asyncio.Queue()
|
create_task(wait_for_paid_invoices(test_name, on_paid_mock)())
|
||||||
register_invoice_listener(invoice_queue, test_name)
|
|
||||||
|
|
||||||
payment = await create_invoice(
|
payment = await create_invoice(
|
||||||
wallet_id=to_wallet.id,
|
wallet_id=to_wallet.id,
|
||||||
amount=123,
|
amount=123,
|
||||||
@@ -248,17 +244,15 @@ async def test_notification_for_internal_payment(to_wallet: Wallet):
|
|||||||
)
|
)
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
while True:
|
assert on_paid_mock.call_count == 1
|
||||||
_payment: Payment = invoice_queue.get_nowait() # raises if queue empty
|
_payment = on_paid_mock.call_args_list[0][0][0]
|
||||||
assert _payment
|
|
||||||
if _payment.memo == test_name:
|
|
||||||
assert _payment.status == PaymentState.SUCCESS.value
|
|
||||||
assert _payment.bolt11 == payment.bolt11
|
|
||||||
assert _payment.amount == 123_000
|
|
||||||
updated_payment = await get_payment(_payment.checking_id)
|
|
||||||
assert updated_payment.webhook_status == "404"
|
|
||||||
|
|
||||||
break # we found our payment, success
|
assert _payment.memo == test_name
|
||||||
|
assert _payment.status == PaymentState.SUCCESS.value
|
||||||
|
assert _payment.bolt11 == payment.bolt11
|
||||||
|
assert _payment.amount == 123_000
|
||||||
|
updated_payment = await get_payment(_payment.checking_id)
|
||||||
|
assert updated_payment.webhook_status == "404"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
|
|||||||
@@ -2537,21 +2537,19 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tornado"
|
name = "tornado"
|
||||||
version = "6.5.2"
|
version = "6.5.5"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/09/ce/1eb500eae19f4648281bb2186927bb062d2438c2e5093d1360391afd2f90/tornado-6.5.2.tar.gz", hash = "sha256:ab53c8f9a0fa351e2c0741284e06c7a45da86afb544133201c5cc8578eb076a0", size = 510821, upload-time = "2025-08-08T18:27:00.78Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/f6/48/6a7529df2c9cc12efd2e8f5dd219516184d703b34c06786809670df5b3bd/tornado-6.5.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2436822940d37cde62771cff8774f4f00b3c8024fe482e16ca8387b8a2724db6", size = 442563, upload-time = "2025-08-08T18:26:42.945Z" },
|
{ url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/b5/9b575a0ed3e50b00c40b08cbce82eb618229091d09f6d14bce80fc01cb0b/tornado-6.5.2-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:583a52c7aa94ee046854ba81d9ebb6c81ec0fd30386d96f7640c96dad45a03ef", size = 440729, upload-time = "2025-08-08T18:26:44.473Z" },
|
{ url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1b/4e/619174f52b120efcf23633c817fd3fed867c30bff785e2cd5a53a70e483c/tornado-6.5.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0fe179f28d597deab2842b86ed4060deec7388f1fd9c1b4a41adf8af058907e", size = 444295, upload-time = "2025-08-08T18:26:46.021Z" },
|
{ url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/95/fa/87b41709552bbd393c85dd18e4e3499dcd8983f66e7972926db8d96aa065/tornado-6.5.2-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b186e85d1e3536d69583d2298423744740986018e393d0321df7340e71898882", size = 443644, upload-time = "2025-08-08T18:26:47.625Z" },
|
{ url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/f9/41/fb15f06e33d7430ca89420283a8762a4e6b8025b800ea51796ab5e6d9559/tornado-6.5.2-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e792706668c87709709c18b353da1f7662317b563ff69f00bab83595940c7108", size = 443878, upload-time = "2025-08-08T18:26:50.599Z" },
|
{ url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/11/92/fe6d57da897776ad2e01e279170ea8ae726755b045fe5ac73b75357a5a3f/tornado-6.5.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:06ceb1300fd70cb20e43b1ad8aaee0266e69e7ced38fa910ad2e03285009ce7c", size = 444549, upload-time = "2025-08-08T18:26:51.864Z" },
|
{ url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/02/c8f4f6c9204526daf3d760f4aa555a7a33ad0e60843eac025ccfd6ff4a93/tornado-6.5.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:74db443e0f5251be86cbf37929f84d8c20c27a355dd452a5cfa2aada0d001ec4", size = 443973, upload-time = "2025-08-08T18:26:53.625Z" },
|
{ url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/2d/f5f5707b655ce2317190183868cd0f6822a1121b4baeae509ceb9590d0bd/tornado-6.5.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b5e735ab2889d7ed33b32a459cac490eda71a1ba6857b0118de476ab6c366c04", size = 443954, upload-time = "2025-08-08T18:26:55.072Z" },
|
{ url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/e8/59/593bd0f40f7355806bf6573b47b8c22f8e1374c9b6fd03114bd6b7a3dcfd/tornado-6.5.2-cp39-abi3-win32.whl", hash = "sha256:c6f29e94d9b37a95013bb669616352ddb82e3bfe8326fccee50583caebc8a5f0", size = 445023, upload-time = "2025-08-08T18:26:56.677Z" },
|
{ url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/2a/f609b420c2f564a748a2d80ebfb2ee02a73ca80223af712fca591386cafb/tornado-6.5.2-cp39-abi3-win_amd64.whl", hash = "sha256:e56a5af51cc30dd2cae649429af65ca2f6571da29504a07995175df14c18f35f", size = 445427, upload-time = "2025-08-08T18:26:57.91Z" },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5e/4f/e1f65e8f8c76d73658b33d33b81eed4322fb5085350e4328d5c956f0c8f9/tornado-6.5.2-cp39-abi3-win_arm64.whl", hash = "sha256:d6c33dc3672e3a1f3618eb63b7ef4683a7688e7b9e6e8f0d9aa5726360a004af", size = 444456, upload-time = "2025-08-08T18:26:59.207Z" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user