commit
592f2b6afa
|
@ -1,13 +0,0 @@
|
||||||
# Gerty
|
|
||||||
|
|
||||||
## Your desktop bitcoin assistant
|
|
||||||
|
|
||||||
Buy here `<link>`
|
|
||||||
|
|
||||||
blah blah blah
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
1. Enable extension
|
|
||||||
2. Fill out form
|
|
||||||
3. point gerty at the server and give it the Gerty ID
|
|
|
@ -1,26 +0,0 @@
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
|
|
||||||
from lnbits.db import Database
|
|
||||||
from lnbits.helpers import template_renderer
|
|
||||||
|
|
||||||
db = Database("ext_gerty")
|
|
||||||
|
|
||||||
gerty_static_files = [
|
|
||||||
{
|
|
||||||
"path": "/gerty/static",
|
|
||||||
"app": StaticFiles(packages=[("lnbits", "extensions/gerty/static")]),
|
|
||||||
"name": "gerty_static",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
gerty_ext: APIRouter = APIRouter(prefix="/gerty", tags=["Gerty"])
|
|
||||||
|
|
||||||
|
|
||||||
def gerty_renderer():
|
|
||||||
return template_renderer(["lnbits/extensions/gerty/templates"])
|
|
||||||
|
|
||||||
|
|
||||||
from .views import * # noqa: F401,F403
|
|
||||||
from .views_api import * # noqa: F401,F403
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Gerty",
|
|
||||||
"short_description": "Desktop bitcoin Assistant",
|
|
||||||
"tile": "/gerty/static/gerty.png",
|
|
||||||
"contributors": ["arcbtc", "blackcoffeebtc"]
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
import json
|
|
||||||
import time
|
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
|
|
||||||
from . import db
|
|
||||||
from .models import Gerty, MempoolEndpoint
|
|
||||||
|
|
||||||
|
|
||||||
async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
|
|
||||||
gerty_id = urlsafe_short_hash()
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO gerty.gertys (
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
utc_offset,
|
|
||||||
type,
|
|
||||||
wallet,
|
|
||||||
lnbits_wallets,
|
|
||||||
mempool_endpoint,
|
|
||||||
exchange,
|
|
||||||
display_preferences,
|
|
||||||
refresh_time,
|
|
||||||
urls
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
gerty_id,
|
|
||||||
data.name,
|
|
||||||
data.utc_offset,
|
|
||||||
data.type,
|
|
||||||
wallet_id,
|
|
||||||
data.lnbits_wallets,
|
|
||||||
data.mempool_endpoint,
|
|
||||||
data.exchange,
|
|
||||||
data.display_preferences,
|
|
||||||
data.refresh_time,
|
|
||||||
data.urls,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
assert gerty, "Newly created gerty couldn't be retrieved"
|
|
||||||
return gerty
|
|
||||||
|
|
||||||
|
|
||||||
async def update_gerty(gerty_id: str, **kwargs) -> Optional[Gerty]:
|
|
||||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
|
||||||
await db.execute(
|
|
||||||
f"UPDATE gerty.gertys SET {q} WHERE id = ?", (*kwargs.values(), gerty_id)
|
|
||||||
)
|
|
||||||
|
|
||||||
return await get_gerty(gerty_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_gerty(gerty_id: str) -> Optional[Gerty]:
|
|
||||||
row = await db.fetchone("SELECT * FROM gerty.gertys WHERE id = ?", (gerty_id,))
|
|
||||||
return Gerty(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_gertys(wallet_ids: Union[str, List[str]]) -> List[Gerty]:
|
|
||||||
if isinstance(wallet_ids, str):
|
|
||||||
wallet_ids = [wallet_ids]
|
|
||||||
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
f"SELECT * FROM gerty.gertys WHERE wallet IN ({q})", (*wallet_ids,)
|
|
||||||
)
|
|
||||||
|
|
||||||
return [Gerty(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_gerty(gerty_id: str) -> None:
|
|
||||||
await db.execute("DELETE FROM gerty.gertys WHERE id = ?", (gerty_id,))
|
|
||||||
|
|
||||||
|
|
||||||
#############MEMPOOL###########
|
|
||||||
|
|
||||||
|
|
||||||
async def get_mempool_info(endPoint: str, gerty) -> dict:
|
|
||||||
logger.debug(endPoint)
|
|
||||||
endpoints = MempoolEndpoint()
|
|
||||||
url = ""
|
|
||||||
for endpoint in endpoints:
|
|
||||||
if endPoint == endpoint[0]:
|
|
||||||
url = endpoint[1]
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM gerty.mempool WHERE endpoint = ? AND mempool_endpoint = ?",
|
|
||||||
(
|
|
||||||
endPoint,
|
|
||||||
gerty.mempool_endpoint,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if not row:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
response = await client.get(gerty.mempool_endpoint + url)
|
|
||||||
logger.debug(gerty.mempool_endpoint + url)
|
|
||||||
mempool_id = urlsafe_short_hash()
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO gerty.mempool (
|
|
||||||
id,
|
|
||||||
data,
|
|
||||||
endpoint,
|
|
||||||
time,
|
|
||||||
mempool_endpoint
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
mempool_id,
|
|
||||||
json.dumps(response.json()),
|
|
||||||
endPoint,
|
|
||||||
time.time(),
|
|
||||||
gerty.mempool_endpoint,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return response.json()
|
|
||||||
if float(time.time()) - row.time > 20:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
response = await client.get(gerty.mempool_endpoint + url)
|
|
||||||
await db.execute(
|
|
||||||
"UPDATE gerty.mempool SET data = ?, time = ? WHERE endpoint = ? AND mempool_endpoint = ?",
|
|
||||||
(
|
|
||||||
json.dumps(response.json()),
|
|
||||||
time.time(),
|
|
||||||
endPoint,
|
|
||||||
gerty.mempool_endpoint,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return response.json()
|
|
||||||
return json.loads(row.data)
|
|
|
@ -1,942 +0,0 @@
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import textwrap
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import httpx
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_wallet_for_key
|
|
||||||
from lnbits.settings import settings
|
|
||||||
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
|
|
||||||
|
|
||||||
from .crud import get_mempool_info
|
|
||||||
from .number_prefixer import * # noqa: F403
|
|
||||||
|
|
||||||
|
|
||||||
def get_percent_difference(current, previous, precision=3):
|
|
||||||
difference = (current - previous) / current * 100
|
|
||||||
return "{0}{1}%".format("+" if difference > 0 else "", round(difference, precision))
|
|
||||||
|
|
||||||
|
|
||||||
# A helper function get a nicely formated dict for the text
|
|
||||||
def get_text_item_dict(
|
|
||||||
text: str,
|
|
||||||
font_size: int,
|
|
||||||
x_pos: int = -1,
|
|
||||||
y_pos: int = -1,
|
|
||||||
gerty_type: str = "Gerty",
|
|
||||||
):
|
|
||||||
# Get line size by font size
|
|
||||||
line_width = 20
|
|
||||||
if font_size <= 12:
|
|
||||||
line_width = 60
|
|
||||||
elif font_size <= 15:
|
|
||||||
line_width = 45
|
|
||||||
elif font_size <= 20:
|
|
||||||
line_width = 35
|
|
||||||
elif font_size <= 40:
|
|
||||||
line_width = 25
|
|
||||||
|
|
||||||
# wrap the text
|
|
||||||
wrapper = textwrap.TextWrapper(width=line_width)
|
|
||||||
word_list = wrapper.wrap(text=text)
|
|
||||||
# logger.debug("number of chars = {0}".format(len(text)))
|
|
||||||
|
|
||||||
multilineText = "\n".join(word_list)
|
|
||||||
# logger.debug("number of lines = {0}".format(len(word_list)))
|
|
||||||
|
|
||||||
# logger.debug('multilineText')
|
|
||||||
# logger.debug(multilineText)
|
|
||||||
|
|
||||||
data_text = {"value": multilineText, "size": font_size}
|
|
||||||
if x_pos == -1 and y_pos == -1:
|
|
||||||
data_text["position"] = "center"
|
|
||||||
else:
|
|
||||||
data_text["x"] = x_pos if x_pos > 0 else 0
|
|
||||||
data_text["y"] = y_pos if x_pos > 0 else 0
|
|
||||||
return data_text
|
|
||||||
|
|
||||||
|
|
||||||
def get_date_suffix(dayNumber):
|
|
||||||
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
|
|
||||||
return "th"
|
|
||||||
else:
|
|
||||||
return ["st", "nd", "rd"][dayNumber % 10 - 1]
|
|
||||||
|
|
||||||
|
|
||||||
def get_time_remaining(seconds, granularity=2):
|
|
||||||
intervals = (
|
|
||||||
# ('weeks', 604800), # 60 * 60 * 24 * 7
|
|
||||||
("days", 86400), # 60 * 60 * 24
|
|
||||||
("hours", 3600), # 60 * 60
|
|
||||||
("minutes", 60),
|
|
||||||
("seconds", 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for name, count in intervals:
|
|
||||||
value = seconds // count
|
|
||||||
if value:
|
|
||||||
seconds -= value * count
|
|
||||||
if value == 1:
|
|
||||||
name = name.rstrip("s")
|
|
||||||
result.append("{} {}".format(round(value), name))
|
|
||||||
return ", ".join(result[:granularity])
|
|
||||||
|
|
||||||
|
|
||||||
# format a number for nice display output
|
|
||||||
def format_number(number, precision=None):
|
|
||||||
return "{:,}".format(round(number, precision))
|
|
||||||
|
|
||||||
|
|
||||||
async def get_mining_dashboard(gerty):
|
|
||||||
areas = []
|
|
||||||
if isinstance(gerty.mempool_endpoint, str):
|
|
||||||
# current hashrate
|
|
||||||
r = await get_mempool_info("hashrate_1w", gerty)
|
|
||||||
data = r
|
|
||||||
hashrateNow = data["currentHashrate"]
|
|
||||||
hashrateOneWeekAgo = data["hashrates"][6]["avgHashrate"]
|
|
||||||
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current hashrate", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}hash".format(
|
|
||||||
si_format(hashrateNow, 6, True, " ") # noqa: F405
|
|
||||||
),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} vs 7 days ago".format(
|
|
||||||
get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)
|
|
||||||
),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
r = await get_mempool_info("difficulty_adjustment", gerty)
|
|
||||||
|
|
||||||
# timeAvg
|
|
||||||
text = []
|
|
||||||
progress = "{0}%".format(round(r["progressPercent"], 2))
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Progress through current epoch",
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text=progress, font_size=40, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
# difficulty adjustment
|
|
||||||
text = []
|
|
||||||
stat = r["remainingTime"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Time to next difficulty adjustment",
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=get_time_remaining(stat / 1000, 3),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
# difficultyChange
|
|
||||||
text = []
|
|
||||||
difficultyChange = round(r["difficultyChange"], 2)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Estimated difficulty change",
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}{1}%".format(
|
|
||||||
"+" if difficultyChange > 0 else "", round(difficultyChange, 2)
|
|
||||||
),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
r = await get_mempool_info("hashrate_1m", gerty)
|
|
||||||
data = r
|
|
||||||
stat = {}
|
|
||||||
stat["current"] = data["currentDifficulty"]
|
|
||||||
stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2]["difficulty"]
|
|
||||||
return areas
|
|
||||||
|
|
||||||
|
|
||||||
async def get_lightning_stats(gerty):
|
|
||||||
data = await get_mempool_info("statistics", gerty)
|
|
||||||
areas = []
|
|
||||||
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text="Channel Count", font_size=12, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(data["latest"]["channel_count"]),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=data["latest"]["channel_count"],
|
|
||||||
previous=data["previous"]["channel_count"],
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} in last 7 days".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text="Number of Nodes", font_size=12, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(data["latest"]["node_count"]),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=data["latest"]["node_count"], previous=data["previous"]["node_count"]
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} in last 7 days".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text="Total Capacity", font_size=12, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
avg_capacity = float(data["latest"]["total_capacity"]) / float(100000000)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} BTC".format(format_number(avg_capacity, 2)),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=data["latest"]["total_capacity"],
|
|
||||||
previous=data["previous"]["total_capacity"],
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} in last 7 days".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Average Channel Capacity", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} sats".format(format_number(data["latest"]["avg_capacity"])),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=data["latest"]["avg_capacity"],
|
|
||||||
previous=data["previous"]["avg_capacity"],
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} in last 7 days".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
return areas
|
|
||||||
|
|
||||||
|
|
||||||
def get_next_update_time(sleep_time_seconds: int = 0, utc_offset: int = 0):
|
|
||||||
utc_now = datetime.utcnow()
|
|
||||||
next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
|
|
||||||
local_refresh_time = next_refresh_time + timedelta(hours=utc_offset)
|
|
||||||
return "{0} {1}".format(
|
|
||||||
"I'll wake up at" if gerty_should_sleep(utc_offset) else "Next update at",
|
|
||||||
local_refresh_time.strftime("%H:%M"),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def gerty_should_sleep(utc_offset: int = 0):
|
|
||||||
utc_now = datetime.utcnow()
|
|
||||||
local_time = utc_now + timedelta(hours=utc_offset)
|
|
||||||
hours = int(local_time.strftime("%H"))
|
|
||||||
if hours >= 22 and hours <= 23:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
async def get_mining_stat(stat_slug: str, gerty):
|
|
||||||
text = []
|
|
||||||
if stat_slug == "mining_current_hash_rate":
|
|
||||||
stat = await api_get_mining_stat(stat_slug, gerty)
|
|
||||||
current = "{0}hash".format(
|
|
||||||
si_format(stat["current"], 6, True, " ") # noqa: F405
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current Mining Hashrate", font_size=20, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text=current, font_size=40, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
# compare vs previous time period
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=stat["current"], previous=stat["1w"]
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} in last 7 days".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif stat_slug == "mining_current_difficulty":
|
|
||||||
stat = await api_get_mining_stat(stat_slug, gerty)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current Mining Difficulty", font_size=20, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(stat["current"]), font_size=40, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
difference = get_percent_difference(
|
|
||||||
current=stat["current"], previous=stat["previous"]
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} since last adjustment".format(difference),
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# text.append(get_text_item_dict("Required threshold for mining proof-of-work", 12))
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
async def api_get_mining_stat(stat_slug: str, gerty):
|
|
||||||
stat = {}
|
|
||||||
if stat_slug == "mining_current_hash_rate":
|
|
||||||
r = await get_mempool_info("hashrate_1m", gerty)
|
|
||||||
data = r
|
|
||||||
stat["current"] = data["currentHashrate"]
|
|
||||||
stat["1w"] = data["hashrates"][len(data["hashrates"]) - 7]["avgHashrate"]
|
|
||||||
elif stat_slug == "mining_current_difficulty":
|
|
||||||
r = await get_mempool_info("hashrate_1m", gerty)
|
|
||||||
data = r
|
|
||||||
stat["current"] = data["currentDifficulty"]
|
|
||||||
stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2]["difficulty"]
|
|
||||||
return stat
|
|
||||||
|
|
||||||
|
|
||||||
###########################################
|
|
||||||
|
|
||||||
|
|
||||||
async def get_satoshi():
|
|
||||||
maxQuoteLength = 186
|
|
||||||
with open(
|
|
||||||
os.path.join(settings.lnbits_path, "extensions/gerty/static/satoshi.json")
|
|
||||||
) as fd:
|
|
||||||
satoshiQuotes = json.load(fd)
|
|
||||||
quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
|
|
||||||
# logger.debug(quote.text)
|
|
||||||
if len(quote["text"]) > maxQuoteLength:
|
|
||||||
logger.trace("Quote is too long, getting another")
|
|
||||||
return await get_satoshi()
|
|
||||||
else:
|
|
||||||
return quote
|
|
||||||
|
|
||||||
|
|
||||||
# Get a screen slug by its position in the screens_list
|
|
||||||
def get_screen_slug_by_index(index: int, screens_list):
|
|
||||||
if index <= len(screens_list) - 1:
|
|
||||||
return list(screens_list)[index - 1]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Get a list of text items for the screen number
|
|
||||||
async def get_screen_data(screen_num: int, screens_list: list, gerty):
|
|
||||||
screen_slug = get_screen_slug_by_index(screen_num, screens_list)
|
|
||||||
# first get the relevant slug from the display_preferences
|
|
||||||
areas: List = []
|
|
||||||
title = ""
|
|
||||||
|
|
||||||
if screen_slug == "dashboard":
|
|
||||||
title = gerty.name
|
|
||||||
areas = await get_dashboard(gerty)
|
|
||||||
|
|
||||||
if screen_slug == "lnbits_wallets_balance":
|
|
||||||
wallets = await get_lnbits_wallet_balances(gerty)
|
|
||||||
|
|
||||||
for wallet in wallets:
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}'s Wallet".format(wallet["name"]),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} sats".format(format_number(wallet["balance"])),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
elif screen_slug == "url_checker":
|
|
||||||
for url in json.loads(gerty.urls):
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
text = []
|
|
||||||
try:
|
|
||||||
response = await client.get(url)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=make_url_readable(url),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=str(response.status_code),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=make_url_readable(url),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=str("DOWN"),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
elif screen_slug == "fun_satoshi_quotes":
|
|
||||||
areas.append(await get_satoshi_quotes(gerty))
|
|
||||||
elif screen_slug == "fun_exchange_market_rate":
|
|
||||||
areas.append(await get_exchange_rate(gerty))
|
|
||||||
elif screen_slug == "onchain_difficulty_epoch_progress":
|
|
||||||
areas.append(await get_onchain_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "onchain_block_height":
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Block Height",
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(await get_mempool_info("tip_height", gerty)),
|
|
||||||
font_size=80,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
elif screen_slug == "onchain_difficulty_retarget_date":
|
|
||||||
areas.append(await get_onchain_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "onchain_difficulty_blocks_remaining":
|
|
||||||
areas.append(await get_onchain_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
|
|
||||||
areas.append(await get_onchain_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "dashboard_onchain":
|
|
||||||
title = "Onchain Data"
|
|
||||||
areas = await get_onchain_dashboard(gerty)
|
|
||||||
elif screen_slug == "mempool_recommended_fees":
|
|
||||||
areas.append(await get_mempool_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "mempool_tx_count":
|
|
||||||
areas.append(await get_mempool_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "mining_current_hash_rate":
|
|
||||||
areas.append(await get_mining_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "mining_current_difficulty":
|
|
||||||
areas.append(await get_mining_stat(screen_slug, gerty))
|
|
||||||
elif screen_slug == "dashboard_mining":
|
|
||||||
title = "Mining Data"
|
|
||||||
areas = await get_mining_dashboard(gerty)
|
|
||||||
elif screen_slug == "lightning_dashboard":
|
|
||||||
title = "Lightning Network"
|
|
||||||
areas = await get_lightning_stats(gerty)
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"title": title,
|
|
||||||
"areas": areas,
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
# Get the dashboard screen
|
|
||||||
async def get_dashboard(gerty):
|
|
||||||
areas = []
|
|
||||||
# XC rate
|
|
||||||
text = []
|
|
||||||
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(amount), font_size=40, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="BTC{0} price".format(gerty.exchange),
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
# balance
|
|
||||||
text = []
|
|
||||||
wallets = await get_lnbits_wallet_balances(gerty)
|
|
||||||
text = []
|
|
||||||
for wallet in wallets:
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}".format(wallet["name"]), font_size=15, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} sats".format(format_number(wallet["balance"])),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
# Mempool fees
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=format_number(await get_mempool_info("tip_height", gerty)),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current block height", font_size=15, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
# difficulty adjustment time
|
|
||||||
text = []
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=await get_time_remaining_next_difficulty_adjustment(gerty) or "0",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="until next difficulty adjustment", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
return areas
|
|
||||||
|
|
||||||
|
|
||||||
async def get_lnbits_wallet_balances(gerty):
|
|
||||||
# Get Wallet info
|
|
||||||
wallets = []
|
|
||||||
if gerty.lnbits_wallets != "":
|
|
||||||
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
|
|
||||||
wallet = await get_wallet_for_key(key=lnbits_wallet)
|
|
||||||
if wallet:
|
|
||||||
wallets.append(
|
|
||||||
{
|
|
||||||
"name": wallet.name,
|
|
||||||
"balance": wallet.balance_msat / 1000,
|
|
||||||
"inkey": wallet.inkey,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return wallets
|
|
||||||
|
|
||||||
|
|
||||||
async def get_placeholder_text(gerty):
|
|
||||||
return [
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Some placeholder text",
|
|
||||||
x_pos=15,
|
|
||||||
y_pos=10,
|
|
||||||
font_size=50,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
),
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Some placeholder text",
|
|
||||||
x_pos=15,
|
|
||||||
y_pos=10,
|
|
||||||
font_size=50,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_satoshi_quotes(gerty):
|
|
||||||
# Get Satoshi quotes
|
|
||||||
text = []
|
|
||||||
quote = await get_satoshi()
|
|
||||||
if quote:
|
|
||||||
if quote["text"]:
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=quote["text"], font_size=15, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if quote["date"]:
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Satoshi Nakamoto - {0}".format(quote["date"]),
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
# Get Exchange Value
|
|
||||||
async def get_exchange_rate(gerty):
|
|
||||||
text = []
|
|
||||||
if gerty.exchange != "":
|
|
||||||
try:
|
|
||||||
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
|
|
||||||
if amount:
|
|
||||||
price = format_number(amount)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current {0}/BTC price".format(gerty.exchange),
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text=price, font_size=80, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
async def get_onchain_stat(stat_slug: str, gerty):
|
|
||||||
text = []
|
|
||||||
if (
|
|
||||||
stat_slug == "onchain_difficulty_epoch_progress"
|
|
||||||
or stat_slug == "onchain_difficulty_retarget_date"
|
|
||||||
or stat_slug == "onchain_difficulty_blocks_remaining"
|
|
||||||
or stat_slug == "onchain_difficulty_epoch_time_remaining"
|
|
||||||
):
|
|
||||||
r = await get_mempool_info("difficulty_adjustment", gerty)
|
|
||||||
if stat_slug == "onchain_difficulty_epoch_progress":
|
|
||||||
stat = round(r["progressPercent"])
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Progress through current difficulty epoch",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}%".format(stat), font_size=80, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif stat_slug == "onchain_difficulty_retarget_date":
|
|
||||||
stat = r["estimatedRetargetDate"]
|
|
||||||
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Date of next difficulty adjustment",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text=dt, font_size=40, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
elif stat_slug == "onchain_difficulty_blocks_remaining":
|
|
||||||
stat = r["remainingBlocks"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Blocks until next difficulty adjustment",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}".format(format_number(stat)),
|
|
||||||
font_size=80,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
|
|
||||||
stat = r["remainingTime"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Time until next difficulty adjustment",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text=get_time_remaining(stat / 1000, 4),
|
|
||||||
font_size=20,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
async def get_onchain_dashboard(gerty):
|
|
||||||
areas = []
|
|
||||||
if isinstance(gerty.mempool_endpoint, str):
|
|
||||||
text = []
|
|
||||||
stat = (format_number(await get_mempool_info("tip_height", gerty)),)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Current block height", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(text=stat[0], font_size=40, gerty_type=gerty.type)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
r = await get_mempool_info("difficulty_adjustment", gerty)
|
|
||||||
text = []
|
|
||||||
stat = round(r["progressPercent"])
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Progress through current epoch",
|
|
||||||
font_size=12,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}%".format(stat), font_size=40, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
stat = r["estimatedRetargetDate"]
|
|
||||||
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Date of next adjustment", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(get_text_item_dict(text=dt, font_size=20, gerty_type=gerty.type))
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
text = []
|
|
||||||
stat = r["remainingBlocks"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Blocks until adjustment", font_size=12, gerty_type=gerty.type
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}".format(format_number(stat)),
|
|
||||||
font_size=40,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
areas.append(text)
|
|
||||||
|
|
||||||
return areas
|
|
||||||
|
|
||||||
|
|
||||||
async def get_time_remaining_next_difficulty_adjustment(gerty):
|
|
||||||
if isinstance(gerty.mempool_endpoint, str):
|
|
||||||
r = await get_mempool_info("difficulty_adjustment", gerty)
|
|
||||||
stat = r["remainingTime"]
|
|
||||||
time = get_time_remaining(stat / 1000, 3)
|
|
||||||
return time
|
|
||||||
|
|
||||||
|
|
||||||
async def get_mempool_stat(stat_slug: str, gerty):
|
|
||||||
text = []
|
|
||||||
if isinstance(gerty.mempool_endpoint, str):
|
|
||||||
if stat_slug == "mempool_tx_count":
|
|
||||||
r = await get_mempool_info("mempool", gerty)
|
|
||||||
if stat_slug == "mempool_tx_count":
|
|
||||||
stat = round(r["count"])
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="Transactions in the mempool",
|
|
||||||
font_size=15,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0}".format(format_number(stat)),
|
|
||||||
font_size=80,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif stat_slug == "mempool_recommended_fees":
|
|
||||||
y_offset = 60
|
|
||||||
fees = await get_mempool_info("fees_recommended", gerty)
|
|
||||||
pos_y = 80 + y_offset
|
|
||||||
text.append(get_text_item_dict("mempool.space", 40, 160, pos_y, gerty.type))
|
|
||||||
pos_y = 180 + y_offset
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict("Recommended Tx Fees", 20, 240, pos_y, gerty.type)
|
|
||||||
)
|
|
||||||
|
|
||||||
pos_y = 280 + y_offset
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict("{0}".format("None"), 15, 30, pos_y, gerty.type)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict("{0}".format("Low"), 15, 235, pos_y, gerty.type)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict("{0}".format("Medium"), 15, 460, pos_y, gerty.type)
|
|
||||||
)
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict("{0}".format("High"), 15, 750, pos_y, gerty.type)
|
|
||||||
)
|
|
||||||
|
|
||||||
pos_y = 340 + y_offset
|
|
||||||
font_size = 15
|
|
||||||
fee_append = "/vB"
|
|
||||||
fee_rate = fees["economyFee"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} {1}{2}".format(
|
|
||||||
format_number(fee_rate),
|
|
||||||
("sat" if fee_rate == 1 else "sats"),
|
|
||||||
fee_append,
|
|
||||||
),
|
|
||||||
font_size=font_size,
|
|
||||||
x_pos=30,
|
|
||||||
y_pos=pos_y,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fee_rate = fees["hourFee"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} {1}{2}".format(
|
|
||||||
format_number(fee_rate),
|
|
||||||
("sat" if fee_rate == 1 else "sats"),
|
|
||||||
fee_append,
|
|
||||||
),
|
|
||||||
font_size=font_size,
|
|
||||||
x_pos=235,
|
|
||||||
y_pos=pos_y,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fee_rate = fees["halfHourFee"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} {1}{2}".format(
|
|
||||||
format_number(fee_rate),
|
|
||||||
("sat" if fee_rate == 1 else "sats"),
|
|
||||||
fee_append,
|
|
||||||
),
|
|
||||||
font_size=font_size,
|
|
||||||
x_pos=460,
|
|
||||||
y_pos=pos_y,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
fee_rate = fees["fastestFee"]
|
|
||||||
text.append(
|
|
||||||
get_text_item_dict(
|
|
||||||
text="{0} {1}{2}".format(
|
|
||||||
format_number(fee_rate),
|
|
||||||
("sat" if fee_rate == 1 else "sats"),
|
|
||||||
fee_append,
|
|
||||||
),
|
|
||||||
font_size=font_size,
|
|
||||||
x_pos=750,
|
|
||||||
y_pos=pos_y,
|
|
||||||
gerty_type=gerty.type,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return text
|
|
||||||
|
|
||||||
|
|
||||||
def make_url_readable(url: str):
|
|
||||||
return url.replace("https://", "").replace("http://", "").strip("/")
|
|
|
@ -1,105 +0,0 @@
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
async def m001_initial(db):
|
|
||||||
"""
|
|
||||||
Initial Gertys table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE gerty.gertys (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
refresh_time INT,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
lnbits_wallets TEXT,
|
|
||||||
mempool_endpoint TEXT,
|
|
||||||
exchange TEXT,
|
|
||||||
display_preferences TEXT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def m002_add_utc_offset_col(db):
|
|
||||||
"""
|
|
||||||
support for UTC offset
|
|
||||||
"""
|
|
||||||
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;")
|
|
||||||
|
|
||||||
|
|
||||||
async def m003_add_gerty_model_col(db):
|
|
||||||
"""
|
|
||||||
support for Gerty model col
|
|
||||||
"""
|
|
||||||
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN type TEXT;")
|
|
||||||
|
|
||||||
|
|
||||||
#########MEMPOOL MIGRATIONS########
|
|
||||||
|
|
||||||
|
|
||||||
async def m004_initial(db):
|
|
||||||
"""
|
|
||||||
Initial Gertys table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE gerty.mempool (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
mempool_endpoint TEXT NOT NULL,
|
|
||||||
endpoint TEXT NOT NULL,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
time TIMESTAMP
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def m005_add_gerty_model_col(db):
|
|
||||||
"""
|
|
||||||
support for Gerty model col
|
|
||||||
"""
|
|
||||||
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN urls TEXT;")
|
|
||||||
|
|
||||||
|
|
||||||
async def m006_add_gerty_model_col(db):
|
|
||||||
"""
|
|
||||||
Add UUID ID's to links and migrates existing data
|
|
||||||
"""
|
|
||||||
await db.execute("ALTER TABLE gerty.mempool RENAME TO mempool_old")
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE gerty.mempool (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
mempool_endpoint TEXT NOT NULL,
|
|
||||||
endpoint TEXT NOT NULL,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
time FLOAT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
for row in [
|
|
||||||
list(row) for row in await db.fetchall("SELECT * FROM gerty.mempool_old")
|
|
||||||
]:
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO gerty.mempool (
|
|
||||||
id,
|
|
||||||
mempool_endpoint,
|
|
||||||
endpoint,
|
|
||||||
data,
|
|
||||||
time
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
row[0],
|
|
||||||
row[1],
|
|
||||||
row[2],
|
|
||||||
row[3],
|
|
||||||
time.time(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute("DROP TABLE gerty.mempool_old")
|
|
|
@ -1,47 +0,0 @@
|
||||||
from sqlite3 import Row
|
|
||||||
|
|
||||||
from fastapi import Query
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class Gerty(BaseModel):
|
|
||||||
id: str = Query(None)
|
|
||||||
name: str
|
|
||||||
refresh_time: int = Query(None)
|
|
||||||
utc_offset: int = Query(None)
|
|
||||||
wallet: str = Query(None)
|
|
||||||
type: str
|
|
||||||
lnbits_wallets: str = Query(
|
|
||||||
None
|
|
||||||
) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
|
|
||||||
mempool_endpoint: str = Query(None) # Mempool endpoint to use
|
|
||||||
exchange: str = Query(
|
|
||||||
None
|
|
||||||
) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
|
|
||||||
display_preferences: str = Query(None)
|
|
||||||
urls: str = Query(None)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_row(cls, row: Row) -> "Gerty":
|
|
||||||
return cls(**dict(row))
|
|
||||||
|
|
||||||
|
|
||||||
#########MEMPOOL MODELS###########
|
|
||||||
|
|
||||||
|
|
||||||
class MempoolEndpoint(BaseModel):
|
|
||||||
fees_recommended: str = "/api/v1/fees/recommended"
|
|
||||||
hashrate_1w: str = "/api/v1/mining/hashrate/1w"
|
|
||||||
hashrate_1m: str = "/api/v1/mining/hashrate/1m"
|
|
||||||
statistics: str = "/api/v1/lightning/statistics/latest"
|
|
||||||
difficulty_adjustment: str = "/api/v1/difficulty-adjustment"
|
|
||||||
tip_height: str = "/api/blocks/tip/height"
|
|
||||||
mempool: str = "/api/mempool"
|
|
||||||
|
|
||||||
|
|
||||||
class Mempool(BaseModel):
|
|
||||||
id: str = Query(None)
|
|
||||||
mempool_endpoint: str = Query(None)
|
|
||||||
endpoint: str = Query(None)
|
|
||||||
data: str = Query(None)
|
|
||||||
time: int = Query(None)
|
|
|
@ -1,60 +0,0 @@
|
||||||
import math
|
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
|
|
||||||
def si_classifier(val) -> dict:
|
|
||||||
suffixes = {
|
|
||||||
24: {"long_suffix": "yotta", "short_suffix": "Y", "scalar": 10**24},
|
|
||||||
21: {"long_suffix": "zetta", "short_suffix": "Z", "scalar": 10**21},
|
|
||||||
18: {"long_suffix": "exa", "short_suffix": "E", "scalar": 10**18},
|
|
||||||
15: {"long_suffix": "peta", "short_suffix": "P", "scalar": 10**15},
|
|
||||||
12: {"long_suffix": "tera", "short_suffix": "T", "scalar": 10**12},
|
|
||||||
9: {"long_suffix": "giga", "short_suffix": "G", "scalar": 10**9},
|
|
||||||
6: {"long_suffix": "mega", "short_suffix": "M", "scalar": 10**6},
|
|
||||||
3: {"long_suffix": "kilo", "short_suffix": "k", "scalar": 10**3},
|
|
||||||
0: {"long_suffix": "", "short_suffix": "", "scalar": 10**0},
|
|
||||||
-3: {"long_suffix": "milli", "short_suffix": "m", "scalar": 10**-3},
|
|
||||||
-6: {"long_suffix": "micro", "short_suffix": "µ", "scalar": 10**-6},
|
|
||||||
-9: {"long_suffix": "nano", "short_suffix": "n", "scalar": 10**-9},
|
|
||||||
-12: {"long_suffix": "pico", "short_suffix": "p", "scalar": 10**-12},
|
|
||||||
-15: {"long_suffix": "femto", "short_suffix": "f", "scalar": 10**-15},
|
|
||||||
-18: {"long_suffix": "atto", "short_suffix": "a", "scalar": 10**-18},
|
|
||||||
-21: {"long_suffix": "zepto", "short_suffix": "z", "scalar": 10**-21},
|
|
||||||
-24: {"long_suffix": "yocto", "short_suffix": "y", "scalar": 10**-24},
|
|
||||||
}
|
|
||||||
exponent = int(math.floor(math.log10(abs(val)) / 3.0) * 3)
|
|
||||||
suffix = suffixes.get(exponent)
|
|
||||||
assert suffix, f"could not classify: {val}"
|
|
||||||
return suffix
|
|
||||||
|
|
||||||
|
|
||||||
def si_formatter(value) -> Tuple:
|
|
||||||
"""
|
|
||||||
Return a triple of scaled value, short suffix, long suffix, or None if
|
|
||||||
the value cannot be classified.
|
|
||||||
"""
|
|
||||||
classifier = si_classifier(value)
|
|
||||||
scaled = value / classifier["scalar"]
|
|
||||||
return scaled, classifier["short_suffix"], classifier["long_suffix"]
|
|
||||||
|
|
||||||
|
|
||||||
def si_format(value: float, precision=4, long_form=False, separator="") -> str:
|
|
||||||
"""
|
|
||||||
"SI prefix" formatted string: return a string with the given precision
|
|
||||||
and an appropriate order-of-3-magnitudes suffix, e.g.:
|
|
||||||
si_format(1001.0) => '1.00K'
|
|
||||||
si_format(0.00000000123, long_form=True, separator=' ') => '1.230 nano'
|
|
||||||
"""
|
|
||||||
scaled, short_suffix, long_suffix = si_formatter(value)
|
|
||||||
suffix = long_suffix if long_form else short_suffix
|
|
||||||
|
|
||||||
if abs(scaled) < 10:
|
|
||||||
precision = precision - 1
|
|
||||||
elif abs(scaled) < 100:
|
|
||||||
precision = precision - 2
|
|
||||||
else:
|
|
||||||
precision = precision - 3
|
|
||||||
|
|
||||||
return "{scaled:.{precision}f}{separator}{suffix}".format(
|
|
||||||
scaled=scaled, precision=precision, separator=separator, suffix=suffix
|
|
||||||
)
|
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB |
Binary file not shown.
Before Width: | Height: | Size: 33 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,25 +0,0 @@
|
||||||
<q-card-section>
|
|
||||||
<p>
|
|
||||||
Gerty (your bitcoin assistant): Use the software Gerty or
|
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
target="_blank"
|
|
||||||
href="https://shop.lnbits.com/product/gerty-a-bitcoin-assistant"
|
|
||||||
>hardware Gerty</a
|
|
||||||
><br />
|
|
||||||
<small>
|
|
||||||
Created by,
|
|
||||||
<a class="text-secondary" href="https://github.com/blackcoffeexbt"
|
|
||||||
>Black Coffee</a
|
|
||||||
>,
|
|
||||||
<a class="text-secondary" href="https://github.com/benarc"
|
|
||||||
>Ben Arc</a
|
|
||||||
></small
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<a
|
|
||||||
class="text-secondary"
|
|
||||||
href="https://shop.lnbits.com/product/gerty-a-bitcoin-assistant"
|
|
||||||
><img src="/gerty/static/gerty.jpg" style="max-width: 100%"
|
|
||||||
/></a>
|
|
||||||
</q-card-section>
|
|
|
@ -1,252 +0,0 @@
|
||||||
{% extends "public.html" %} {% block toolbar_title %} Gerty: {% raw %}{{
|
|
||||||
gertyname }}{% endraw %}{% endblock %}{% block page %} {% raw %}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="q-pa-md row items-start q-gutter-md"
|
|
||||||
v-if="fun_exchange_market_rate || fun_satoshi_quotes"
|
|
||||||
>
|
|
||||||
<q-card
|
|
||||||
v-if="fun_exchange_market_rate['unit'] != ''"
|
|
||||||
unelevated
|
|
||||||
class="q-pa-sm"
|
|
||||||
style="background: none !important"
|
|
||||||
>
|
|
||||||
<q-card-section class="text-h1 q-pa-none">
|
|
||||||
<small> <b>{{fun_exchange_market_rate["amount"]}}</b></small>
|
|
||||||
<small class="text-h4"
|
|
||||||
>{{fun_exchange_market_rate["unit"].split(" ")[1]}}</small
|
|
||||||
>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
<q-card
|
|
||||||
v-if="fun_satoshi_quotes['quote']"
|
|
||||||
unelevated
|
|
||||||
class="q-pa-none text-body1 blockquote"
|
|
||||||
style="background: none !important"
|
|
||||||
>
|
|
||||||
<blockquote class="text-right" style="max-width: 900px">
|
|
||||||
<p>"{{fun_satoshi_quotes["quote"]}}"</p>
|
|
||||||
<small>~ Satoshi {{fun_satoshi_quotes["date"]}}</small>
|
|
||||||
</blockquote>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="q-pa-md row items-start q-gutter-md"
|
|
||||||
v-if="lnbits_wallets_balance[0]"
|
|
||||||
>
|
|
||||||
<q-card
|
|
||||||
class="q-pa-sm"
|
|
||||||
v-for="(wallet, t) in lnbits_wallets_balance"
|
|
||||||
:style="`background-color: ${wallet.color1} !important`"
|
|
||||||
unelevated
|
|
||||||
class="q-pa-none q-pa-sm"
|
|
||||||
>
|
|
||||||
<q-card-section class="text-h1 q-pa-none">
|
|
||||||
<small> <b>{{wallet["amount"]}}</b></small>
|
|
||||||
<small class="text-h4">({{wallet["name"]}})</small>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="q-pa-md row items-start q-gutter-md"
|
|
||||||
v-if="dashboard_onchain[0] || dashboard_mining[0] || lightning_dashboard[0] || url_checker[0]"
|
|
||||||
>
|
|
||||||
<q-card
|
|
||||||
class="q-pa-sm"
|
|
||||||
v-if="dashboard_onchain[0]"
|
|
||||||
unelevated
|
|
||||||
class="q-pa-sm"
|
|
||||||
>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">Onchain</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<p v-for="(item, t) in dashboard_onchain">
|
|
||||||
<b>{{item[0].value}}: </b>{{item[1].value}}
|
|
||||||
</p>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
<q-card class="q-pa-sm" v-if="dashboard_mining[0]" unelevated class="q-pa-sm">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">Mining</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<p v-for="(item, t) in dashboard_mining">
|
|
||||||
<b>{{item[0].value}}:</b> {{item[1].value}}
|
|
||||||
</p>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
|
|
||||||
<q-card
|
|
||||||
class="q-pa-sm"
|
|
||||||
v-if="lightning_dashboard[0]"
|
|
||||||
unelevated
|
|
||||||
class="q-pa-sm"
|
|
||||||
>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">Lightning (Last 7 days)</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<p v-for="(item, t) in lightning_dashboard">
|
|
||||||
<b>{{item[0].value}}:</b> {{item[1].value}}
|
|
||||||
</p>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
<q-card class="q-pa-sm" v-if="url_checker[0]" unelevated class="q-pa-sm">
|
|
||||||
<q-card-section>
|
|
||||||
<div class="text-h6">Servers to check</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<div class="row q-pb-md" v-for="(item, t) in url_checker">
|
|
||||||
<div class="col-8">
|
|
||||||
<small>
|
|
||||||
<b style="word-wrap: break-word; max-width: 230px; display: block">
|
|
||||||
<a class="text-secondary" class="text-primary">
|
|
||||||
{{item[0].value}}
|
|
||||||
</a>
|
|
||||||
</b>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
<div class="col-4">
|
|
||||||
<q-chip
|
|
||||||
v-if="item[1].value < 300"
|
|
||||||
square
|
|
||||||
size="sm"
|
|
||||||
color="green"
|
|
||||||
text-color="white"
|
|
||||||
icon="sentiment_satisfied"
|
|
||||||
>
|
|
||||||
{{item[1].value}}
|
|
||||||
</q-chip>
|
|
||||||
<q-chip
|
|
||||||
v-else-if="item[1].value >= 300"
|
|
||||||
square
|
|
||||||
size="sm"
|
|
||||||
color="orange"
|
|
||||||
text-color="white"
|
|
||||||
icon="sentiment_dissatisfied"
|
|
||||||
>
|
|
||||||
{{item[1].value}}
|
|
||||||
</q-chip>
|
|
||||||
<q-chip
|
|
||||||
v-else
|
|
||||||
square
|
|
||||||
size="sm"
|
|
||||||
color="red"
|
|
||||||
text-color="white"
|
|
||||||
icon="sentiment_dissatisfied"
|
|
||||||
>
|
|
||||||
{{item[1].value}}
|
|
||||||
</q-chip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endraw %} {% endblock %} {% block scripts %}
|
|
||||||
<script>
|
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
lnbits_wallets_balance: {},
|
|
||||||
dashboard_onchain: {},
|
|
||||||
fun_satoshi_quotes: {},
|
|
||||||
fun_exchange_market_rate: {
|
|
||||||
unit: ''
|
|
||||||
},
|
|
||||||
dashboard_mining: {},
|
|
||||||
lightning_dashboard: {},
|
|
||||||
url_checker: {},
|
|
||||||
dashboard_mining: {},
|
|
||||||
gerty: [],
|
|
||||||
gerty_id: `{{gerty}}`,
|
|
||||||
gertyname: '',
|
|
||||||
walletColors: [
|
|
||||||
{first: '#3f51b5', second: '#1a237e'},
|
|
||||||
{first: '#9c27b0', second: '#4a148c'},
|
|
||||||
{first: '#e91e63', second: '#880e4f'},
|
|
||||||
{first: '#009688', second: '#004d40'},
|
|
||||||
{first: '#ff9800', second: '#e65100'},
|
|
||||||
{first: '#2196f3', second: '#0d47a1'},
|
|
||||||
{first: '#4caf50', second: '#1b5e20'}
|
|
||||||
],
|
|
||||||
gertywallets: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getGertyInfo: async function () {
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/gerty/api/v1/gerty/pages/${this.gerty_id}/${i}`
|
|
||||||
)
|
|
||||||
this.gerty[i] = data
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (let i = 0; i < this.gerty.length; i++) {
|
|
||||||
if (this.gerty[i].screen.group == 'lnbits_wallets_balance') {
|
|
||||||
for (let q = 0; q < this.gerty[i].screen.areas.length; q++) {
|
|
||||||
this.lnbits_wallets_balance[q] = {
|
|
||||||
name: this.gerty[i].screen.areas[q][0].value,
|
|
||||||
amount: this.gerty[i].screen.areas[q][1].value,
|
|
||||||
color1: this.walletColors[q].first,
|
|
||||||
color2: this.walletColors[q].second
|
|
||||||
}
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'url_checker') {
|
|
||||||
this.url_checker = this.gerty[i].screen.areas
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'dashboard_onchain') {
|
|
||||||
this.dashboard_onchain = this.gerty[i].screen.areas
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'dashboard_mining') {
|
|
||||||
this.dashboard_mining = this.gerty[i].screen.areas
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'lightning_dashboard') {
|
|
||||||
this.lightning_dashboard = this.gerty[i].screen.areas
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'fun_satoshi_quotes') {
|
|
||||||
this.fun_satoshi_quotes['quote'] =
|
|
||||||
this.gerty[i].screen.areas[0][0].value
|
|
||||||
this.fun_satoshi_quotes['date'] =
|
|
||||||
this.gerty[i].screen.areas[0][1].value
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
if (this.gerty[i].screen.group == 'fun_exchange_market_rate') {
|
|
||||||
this.fun_exchange_market_rate['unit'] =
|
|
||||||
this.gerty[i].screen.areas[0][0].value
|
|
||||||
this.fun_exchange_market_rate['amount'] =
|
|
||||||
this.gerty[i].screen.areas[0][1].value
|
|
||||||
this.gertyname = this.gerty[i].settings.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(this.getGertyInfo, 20000)
|
|
||||||
this.$forceUpdate()
|
|
||||||
return this.gerty
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: async function () {
|
|
||||||
await this.getGertyInfo()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -1,823 +0,0 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
||||||
%} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
|
||||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<q-btn unelevated color="primary" @click="formDialog.show = true"
|
|
||||||
>New Gerty
|
|
||||||
</q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="text-subtitle1 q-my-none">Gerty</h5>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="gertys"
|
|
||||||
row-key="id"
|
|
||||||
:columns="gertysTable.columns"
|
|
||||||
:pagination.sync="gertysTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th
|
|
||||||
v-for="col in props.cols"
|
|
||||||
:key="col.name"
|
|
||||||
:props="props"
|
|
||||||
:class="`col__${col.name} text-truncate elipsis`"
|
|
||||||
>
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td auto-width>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="sentiment_satisfied"
|
|
||||||
color="green"
|
|
||||||
type="a"
|
|
||||||
:href="props.row.gerty"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<q-tooltip>Launch software Gerty</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="openSettingsModal(props.row.gertyJson)"
|
|
||||||
icon="perm_data_setting"
|
|
||||||
color="primary"
|
|
||||||
>
|
|
||||||
<q-tooltip> Gerty Settings </q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ (col.name == 'tip_options' && col.value ?
|
|
||||||
JSON.parse(col.value).join(", ") : col.value) }}
|
|
||||||
</q-td>
|
|
||||||
<q-td auto-width>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="updateformDialog(props.row.id)"
|
|
||||||
icon="edit"
|
|
||||||
color="light-blue"
|
|
||||||
></q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td auto-width>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
@click="deleteGerty(props.row.id)"
|
|
||||||
icon="cancel"
|
|
||||||
color="pink"
|
|
||||||
></q-btn>
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-12 col-md-5 q-gutter-y-md">
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-9">
|
|
||||||
<h6 class="text-subtitle1 q-my-none">
|
|
||||||
{{ SITE_TITLE }} Gerty extension
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
label="Swagger API"
|
|
||||||
type="a"
|
|
||||||
href="../docs#/gerty"
|
|
||||||
></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pa-none">
|
|
||||||
<q-separator></q-separator>
|
|
||||||
<q-list> {% include "gerty/_api_docs.html" %} </q-list>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<q-dialog
|
|
||||||
v-model="settingsDialog.show"
|
|
||||||
deviceition="top"
|
|
||||||
@hide="closeFormDialog"
|
|
||||||
>
|
|
||||||
<q-card
|
|
||||||
style="width: 700px; max-width: 80vw"
|
|
||||||
class="q-pa-lg lnbits__dialog-card"
|
|
||||||
>
|
|
||||||
<div class="text-h6 text-center">Gerty API URL</div>
|
|
||||||
<center>
|
|
||||||
<q-btn
|
|
||||||
dense
|
|
||||||
outline
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
size="md"
|
|
||||||
@click="copyText(settingsDialog.apiUrl, 'Link copied to clipboard!')"
|
|
||||||
>{% raw %}{{settingsDialog.apiUrl}}{% endraw %}<q-tooltip>
|
|
||||||
Click to Copy URL
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</center>
|
|
||||||
<div class="text-subtitle2">
|
|
||||||
<small> </small>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
|
|
||||||
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
|
||||||
<q-form @submit="sendFormDataGerty" class="q-gutter-md">
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialog.data.name"
|
|
||||||
label="Name"
|
|
||||||
placeholder="Son of Gerty"
|
|
||||||
></q-input>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.fun_satoshi_quotes"
|
|
||||||
val="xs"
|
|
||||||
label="Satoshi Quotes"
|
|
||||||
><q-tooltip
|
|
||||||
>Displays random quotes from Satoshi</q-tooltip
|
|
||||||
></q-checkbox
|
|
||||||
>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.fun_exchange_market_rate"
|
|
||||||
val="xs"
|
|
||||||
label="Fiat to BTC price"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.onchain_block_height"
|
|
||||||
val="xs"
|
|
||||||
label="Block Height"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.lnbits_wallets_balance"
|
|
||||||
val="xs"
|
|
||||||
label="LNbits"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.dashboard_onchain"
|
|
||||||
val="xs"
|
|
||||||
label="Onchain"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.dashboard_mining"
|
|
||||||
val="xs"
|
|
||||||
label="Mining"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.lightning_dashboard"
|
|
||||||
val="xs"
|
|
||||||
label="Lightning"
|
|
||||||
></q-checkbox>
|
|
||||||
<q-checkbox
|
|
||||||
class="q-pl-md"
|
|
||||||
size="xs"
|
|
||||||
v-model="formDialog.data.display_preferences.url_checker"
|
|
||||||
val="xs"
|
|
||||||
label="URL Checker"
|
|
||||||
></q-checkbox>
|
|
||||||
<br />
|
|
||||||
<q-select
|
|
||||||
v-if="formDialog.data.display_preferences.fun_exchange_market_rate"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="formDialog.data.exchange"
|
|
||||||
:options="currencyOptions"
|
|
||||||
label="Exchange rate"
|
|
||||||
></q-select>
|
|
||||||
|
|
||||||
<q-select
|
|
||||||
v-if="formDialog.data.display_preferences.lnbits_wallets_balance"
|
|
||||||
filled
|
|
||||||
multiple
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="formDialog.data.lnbits_wallets"
|
|
||||||
use-input
|
|
||||||
use-chips
|
|
||||||
multiple
|
|
||||||
hide-dropdown-icon
|
|
||||||
new-value-mode="add-unique"
|
|
||||||
label="Invoice keys of wallets to watch"
|
|
||||||
>
|
|
||||||
<q-tooltip>Hit enter to add values</q-tooltip>
|
|
||||||
</q-select>
|
|
||||||
|
|
||||||
<q-select
|
|
||||||
v-if="formDialog.data.display_preferences.url_checker"
|
|
||||||
filled
|
|
||||||
multiple
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="formDialog.data.urls"
|
|
||||||
use-input
|
|
||||||
use-chips
|
|
||||||
multiple
|
|
||||||
hide-dropdown-icon
|
|
||||||
new-value-mode="add-unique"
|
|
||||||
max-values="4"
|
|
||||||
label="URLs to watch."
|
|
||||||
>
|
|
||||||
<q-tooltip>Hit enter to add values</q-tooltip>
|
|
||||||
</q-select>
|
|
||||||
|
|
||||||
<q-toggle
|
|
||||||
label="*Advanced"
|
|
||||||
v-model="toggleStates.advanced"
|
|
||||||
@input="setAdvanced"
|
|
||||||
></q-toggle>
|
|
||||||
<br />
|
|
||||||
<q-input
|
|
||||||
v-if="toggleStates.advanced"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialog.data.mempool_endpoint"
|
|
||||||
label="Mempool link"
|
|
||||||
class="q-pb-sm"
|
|
||||||
>
|
|
||||||
</q-input>
|
|
||||||
<q-input
|
|
||||||
v-if="toggleStates.advanced"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
v-model.trim="formDialog.data.refresh_time"
|
|
||||||
label="Refresh time in seconds"
|
|
||||||
class="q-pb-md"
|
|
||||||
>
|
|
||||||
<q-tooltip
|
|
||||||
>The amount of time in seconds between screen updates
|
|
||||||
</q-tooltip>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="formDialog.data.name == null"
|
|
||||||
type="submit"
|
|
||||||
class="q-mr-md"
|
|
||||||
v-if="!formDialog.data.id"
|
|
||||||
>Create Gerty
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="formDialog.data.name == null"
|
|
||||||
type="submit"
|
|
||||||
>Update Gerty
|
|
||||||
</q-btn>
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
|
||||||
>Cancel
|
|
||||||
</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</div>
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script>
|
|
||||||
var mapGerty = function (obj) {
|
|
||||||
obj.date = Quasar.utils.date.formatDate(
|
|
||||||
new Date(obj.time * 1000),
|
|
||||||
'YYYY-MM-DD HH:mm'
|
|
||||||
)
|
|
||||||
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
|
|
||||||
obj.gerty = ['/gerty/', obj.id].join('')
|
|
||||||
obj.gertyJson = [
|
|
||||||
window.location.origin,
|
|
||||||
'/gerty/api/v1/gerty/pages/',
|
|
||||||
obj.id
|
|
||||||
].join('')
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
toggleStates: {
|
|
||||||
fun: false,
|
|
||||||
onchain: false,
|
|
||||||
mempool: false,
|
|
||||||
mining: false,
|
|
||||||
lightning: false,
|
|
||||||
advanced: false
|
|
||||||
},
|
|
||||||
oldToggleStates: {},
|
|
||||||
gertys: [],
|
|
||||||
currencyOptions: [
|
|
||||||
'USD',
|
|
||||||
'EUR',
|
|
||||||
'GBP',
|
|
||||||
'AED',
|
|
||||||
'AFN',
|
|
||||||
'ALL',
|
|
||||||
'AMD',
|
|
||||||
'ANG',
|
|
||||||
'AOA',
|
|
||||||
'ARS',
|
|
||||||
'AUD',
|
|
||||||
'AWG',
|
|
||||||
'AZN',
|
|
||||||
'BAM',
|
|
||||||
'BBD',
|
|
||||||
'BDT',
|
|
||||||
'BGN',
|
|
||||||
'BHD',
|
|
||||||
'BIF',
|
|
||||||
'BMD',
|
|
||||||
'BND',
|
|
||||||
'BOB',
|
|
||||||
'BRL',
|
|
||||||
'BSD',
|
|
||||||
'BTN',
|
|
||||||
'BWP',
|
|
||||||
'BYN',
|
|
||||||
'BZD',
|
|
||||||
'CAD',
|
|
||||||
'CDF',
|
|
||||||
'CHF',
|
|
||||||
'CLF',
|
|
||||||
'CLP',
|
|
||||||
'CNH',
|
|
||||||
'CNY',
|
|
||||||
'COP',
|
|
||||||
'CRC',
|
|
||||||
'CUC',
|
|
||||||
'CUP',
|
|
||||||
'CVE',
|
|
||||||
'CZK',
|
|
||||||
'DJF',
|
|
||||||
'DKK',
|
|
||||||
'DOP',
|
|
||||||
'DZD',
|
|
||||||
'EGP',
|
|
||||||
'ERN',
|
|
||||||
'ETB',
|
|
||||||
'EUR',
|
|
||||||
'FJD',
|
|
||||||
'FKP',
|
|
||||||
'GBP',
|
|
||||||
'GEL',
|
|
||||||
'GGP',
|
|
||||||
'GHS',
|
|
||||||
'GIP',
|
|
||||||
'GMD',
|
|
||||||
'GNF',
|
|
||||||
'GTQ',
|
|
||||||
'GYD',
|
|
||||||
'HKD',
|
|
||||||
'HNL',
|
|
||||||
'HRK',
|
|
||||||
'HTG',
|
|
||||||
'HUF',
|
|
||||||
'IDR',
|
|
||||||
'ILS',
|
|
||||||
'IMP',
|
|
||||||
'INR',
|
|
||||||
'IQD',
|
|
||||||
'IRR',
|
|
||||||
'IRT',
|
|
||||||
'ISK',
|
|
||||||
'JEP',
|
|
||||||
'JMD',
|
|
||||||
'JOD',
|
|
||||||
'JPY',
|
|
||||||
'KES',
|
|
||||||
'KGS',
|
|
||||||
'KHR',
|
|
||||||
'KMF',
|
|
||||||
'KPW',
|
|
||||||
'KRW',
|
|
||||||
'KWD',
|
|
||||||
'KYD',
|
|
||||||
'KZT',
|
|
||||||
'LAK',
|
|
||||||
'LBP',
|
|
||||||
'LKR',
|
|
||||||
'LRD',
|
|
||||||
'LSL',
|
|
||||||
'LYD',
|
|
||||||
'MAD',
|
|
||||||
'MDL',
|
|
||||||
'MGA',
|
|
||||||
'MKD',
|
|
||||||
'MMK',
|
|
||||||
'MNT',
|
|
||||||
'MOP',
|
|
||||||
'MRO',
|
|
||||||
'MUR',
|
|
||||||
'MVR',
|
|
||||||
'MWK',
|
|
||||||
'MXN',
|
|
||||||
'MYR',
|
|
||||||
'MZN',
|
|
||||||
'NAD',
|
|
||||||
'NGN',
|
|
||||||
'NIO',
|
|
||||||
'NOK',
|
|
||||||
'NPR',
|
|
||||||
'NZD',
|
|
||||||
'OMR',
|
|
||||||
'PAB',
|
|
||||||
'PEN',
|
|
||||||
'PGK',
|
|
||||||
'PHP',
|
|
||||||
'PKR',
|
|
||||||
'PLN',
|
|
||||||
'PYG',
|
|
||||||
'QAR',
|
|
||||||
'RON',
|
|
||||||
'RSD',
|
|
||||||
'RUB',
|
|
||||||
'RWF',
|
|
||||||
'SAR',
|
|
||||||
'SBD',
|
|
||||||
'SCR',
|
|
||||||
'SDG',
|
|
||||||
'SEK',
|
|
||||||
'SGD',
|
|
||||||
'SHP',
|
|
||||||
'SLL',
|
|
||||||
'SOS',
|
|
||||||
'SRD',
|
|
||||||
'SSP',
|
|
||||||
'STD',
|
|
||||||
'SVC',
|
|
||||||
'SYP',
|
|
||||||
'SZL',
|
|
||||||
'THB',
|
|
||||||
'TJS',
|
|
||||||
'TMT',
|
|
||||||
'TND',
|
|
||||||
'TOP',
|
|
||||||
'TRY',
|
|
||||||
'TTD',
|
|
||||||
'TWD',
|
|
||||||
'TZS',
|
|
||||||
'UAH',
|
|
||||||
'UGX',
|
|
||||||
'USD',
|
|
||||||
'UYU',
|
|
||||||
'UZS',
|
|
||||||
'VEF',
|
|
||||||
'VES',
|
|
||||||
'VND',
|
|
||||||
'VUV',
|
|
||||||
'WST',
|
|
||||||
'XAF',
|
|
||||||
'XAG',
|
|
||||||
'XAU',
|
|
||||||
'XCD',
|
|
||||||
'XDR',
|
|
||||||
'XOF',
|
|
||||||
'XPD',
|
|
||||||
'XPF',
|
|
||||||
'XPT',
|
|
||||||
'YER',
|
|
||||||
'ZAR',
|
|
||||||
'ZMW',
|
|
||||||
'ZWL'
|
|
||||||
],
|
|
||||||
gertysTable: {
|
|
||||||
columns: [
|
|
||||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
|
||||||
{
|
|
||||||
name: 'exchange',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Exchange',
|
|
||||||
field: 'exchange'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mempool_endpoint',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Mempool Endpoint',
|
|
||||||
field: 'mempool_endpoint'
|
|
||||||
},
|
|
||||||
{name: 'id', align: 'left', label: 'Gerty ID', field: 'id'}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
settingsDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
formDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
type: 'Mini Gerty',
|
|
||||||
exchange: 'USD',
|
|
||||||
utc_offset: new Date().getTimezoneOffset(),
|
|
||||||
display_preferences: {
|
|
||||||
dashboard: false,
|
|
||||||
fun_satoshi_quotes: false,
|
|
||||||
fun_exchange_market_rate: false,
|
|
||||||
dashboard_onchain: false,
|
|
||||||
mempool_recommended_fees: false,
|
|
||||||
dashboard_mining: false,
|
|
||||||
lightning_dashboard: false,
|
|
||||||
onchain: false,
|
|
||||||
onchain_difficulty_epoch_progress: false,
|
|
||||||
onchain_difficulty_retarget_date: false,
|
|
||||||
onchain_difficulty_blocks_remaining: false,
|
|
||||||
onchain_difficulty_epoch_time_remaining: false,
|
|
||||||
onchain_block_height: false,
|
|
||||||
mempool_tx_count: false,
|
|
||||||
mining_current_hash_rate: false,
|
|
||||||
mining_current_difficulty: false,
|
|
||||||
lnbits_wallets_balance: false,
|
|
||||||
url_checker: false
|
|
||||||
},
|
|
||||||
lnbits_wallets: [],
|
|
||||||
urls: [],
|
|
||||||
mempool_endpoint: 'https://mempool.space',
|
|
||||||
refresh_time: 300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setAdvanced: function () {
|
|
||||||
self = this
|
|
||||||
self.formDialog.data.mempool_endpoint = 'https://mempool.space'
|
|
||||||
self.formDialog.data.refresh_time = 300
|
|
||||||
},
|
|
||||||
setWallets: function () {
|
|
||||||
self = this
|
|
||||||
if (!self.formDialog.data.display_preferences.lnbits_wallets_balance) {
|
|
||||||
self.formDialog.data.lnbits_wallets = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setUrls: function () {
|
|
||||||
self = this
|
|
||||||
if (!self.formDialog.data.display_preferences.url_checker) {
|
|
||||||
self.formDialog.data.urls = []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setOnchain: function () {
|
|
||||||
self = this
|
|
||||||
self.formDialog.data.display_preferences.onchain_difficulty_epoch_progress =
|
|
||||||
self.toggleStates.onchain
|
|
||||||
self.formDialog.data.display_preferences.onchain_difficulty_retarget_date =
|
|
||||||
self.toggleStates.onchain
|
|
||||||
self.formDialog.data.display_preferences.onchain_difficulty_blocks_remaining =
|
|
||||||
!self.toggleStates.onchain
|
|
||||||
self.formDialog.data.display_preferences.onchain_difficulty_epoch_time_remaining =
|
|
||||||
self.toggleStates.onchain
|
|
||||||
self.formDialog.data.display_preferences.onchain_block_height =
|
|
||||||
self.toggleStates.onchain
|
|
||||||
},
|
|
||||||
setMining: function () {
|
|
||||||
self = this
|
|
||||||
self.formDialog.data.display_preferences.mining_current_hash_rate =
|
|
||||||
self.toggleStates.mining
|
|
||||||
self.formDialog.data.display_preferences.mining_current_difficulty =
|
|
||||||
self.toggleStates.mining
|
|
||||||
},
|
|
||||||
closeFormDialog: function () {
|
|
||||||
this.formDialog.data = {
|
|
||||||
utc_offset: 0,
|
|
||||||
lnbits_wallets: [],
|
|
||||||
urls: [],
|
|
||||||
mempool_endpoint: 'https://mempool.space',
|
|
||||||
refresh_time: 300,
|
|
||||||
display_preferences: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getGertys: function () {
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/gerty/api/v1/gerty?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.gertys = response.data.map(function (obj) {
|
|
||||||
return mapGerty(obj)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openSettingsModal: function (apiUrl) {
|
|
||||||
this.settingsDialog.apiUrl = apiUrl
|
|
||||||
this.settingsDialog.show = true
|
|
||||||
},
|
|
||||||
updateformDialog: function (formId) {
|
|
||||||
var gerty = _.findWhere(this.gertys, {id: formId})
|
|
||||||
this.formDialog.data.id = gerty.id
|
|
||||||
this.formDialog.data.name = gerty.name
|
|
||||||
this.formDialog.data.type = gerty.type
|
|
||||||
this.formDialog.data.utc_offset = gerty.utc_offset
|
|
||||||
this.formDialog.data.lnbits_wallets = JSON.parse(gerty.lnbits_wallets)
|
|
||||||
this.formDialog.data.urls = JSON.parse(gerty.urls)
|
|
||||||
;(this.formDialog.data.exchange = gerty.exchange),
|
|
||||||
(this.formDialog.data.mempool_endpoint = gerty.mempool_endpoint),
|
|
||||||
(this.formDialog.data.refresh_time = gerty.refresh_time),
|
|
||||||
(this.formDialog.data.display_preferences = JSON.parse(
|
|
||||||
gerty.display_preferences
|
|
||||||
)),
|
|
||||||
(this.formDialog.show = true)
|
|
||||||
},
|
|
||||||
sendFormDataGerty: function () {
|
|
||||||
if (this.formDialog.data.id) {
|
|
||||||
this.updateGerty(
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
this.formDialog.data
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.createGerty(
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
this.formDialog.data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
createGerty: function () {
|
|
||||||
if (
|
|
||||||
this.formDialog.data.display_preferences.dashboard ||
|
|
||||||
this.formDialog.data.display_preferences.dashboard_onchain ||
|
|
||||||
this.formDialog.data.display_preferences.dashboard_onchain ||
|
|
||||||
this.formDialog.data.display_preferences.lightning_dashboard ||
|
|
||||||
this.formDialog.data.display_preferences.url_checker
|
|
||||||
) {
|
|
||||||
this.formDialog.data.type = 'Gerty'
|
|
||||||
}
|
|
||||||
var data = {
|
|
||||||
name: this.formDialog.data.name,
|
|
||||||
utc_offset: this.formDialog.data.utc_offset,
|
|
||||||
type: this.formDialog.data.type,
|
|
||||||
lnbits_wallets: JSON.stringify(this.formDialog.data.lnbits_wallets),
|
|
||||||
urls: JSON.stringify(this.formDialog.data.urls),
|
|
||||||
exchange: this.formDialog.data.exchange,
|
|
||||||
mempool_endpoint: this.formDialog.data.mempool_endpoint,
|
|
||||||
refresh_time: this.formDialog.data.refresh_time,
|
|
||||||
display_preferences: JSON.stringify(
|
|
||||||
this.formDialog.data.display_preferences
|
|
||||||
)
|
|
||||||
}
|
|
||||||
var self = this
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/gerty/api/v1/gerty',
|
|
||||||
this.g.user.wallets[0].inkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.formDialog.show = false
|
|
||||||
self.gertys.push(mapGerty(response.data))
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateGerty: function (wallet, data) {
|
|
||||||
var self = this
|
|
||||||
if (
|
|
||||||
this.formDialog.data.display_preferences.dashboard ||
|
|
||||||
this.formDialog.data.display_preferences.dashboard_onchain ||
|
|
||||||
this.formDialog.data.display_preferences.dashboard_onchain ||
|
|
||||||
this.formDialog.data.display_preferences.lightning_dashboard ||
|
|
||||||
this.formDialog.data.display_preferences.url_checker
|
|
||||||
) {
|
|
||||||
this.formDialog.data.type = 'Gerty'
|
|
||||||
}
|
|
||||||
data.utc_offset = this.formDialog.data.utc_offset
|
|
||||||
data.type = this.formDialog.data.type
|
|
||||||
data.lnbits_wallets = JSON.stringify(
|
|
||||||
this.formDialog.data.lnbits_wallets
|
|
||||||
)
|
|
||||||
data.urls = JSON.stringify(this.formDialog.data.urls)
|
|
||||||
data.display_preferences = JSON.stringify(
|
|
||||||
this.formDialog.data.display_preferences
|
|
||||||
)
|
|
||||||
LNbits.api
|
|
||||||
.request('PUT', '/gerty/api/v1/gerty/' + data.id, wallet, data)
|
|
||||||
.then(function (response) {
|
|
||||||
self.gertys = _.reject(self.gertys, function (obj) {
|
|
||||||
return obj.id == data.id
|
|
||||||
})
|
|
||||||
self.formDialog.show = false
|
|
||||||
self.gertys.push(mapGerty(response.data))
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteGerty: function (gertyId) {
|
|
||||||
var self = this
|
|
||||||
|
|
||||||
var gerty = _.findWhere(self.gertys, {id: gertyId})
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Are you sure you want to delete this Gerty?')
|
|
||||||
.onOk(function () {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/gerty/api/v1/gerty/' + gertyId,
|
|
||||||
_.findWhere(self.g.user.wallets, {id: gerty.wallet}).adminkey
|
|
||||||
)
|
|
||||||
.then(function (response) {
|
|
||||||
self.gertys = _.reject(self.gertys, function (obj) {
|
|
||||||
return obj.id == gertyId
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
exportCSV: function () {
|
|
||||||
LNbits.utils.exportCSV(this.gertysTable.columns, this.gertys)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isMiniGerty() {
|
|
||||||
return this.formDialog.data.type == 'Mini Gerty'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
if (this.g.user.wallets.length) {
|
|
||||||
this.getGertys()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
'formDialog.data.type': {
|
|
||||||
handler(value) {
|
|
||||||
if (value == 'Mini Gerty') {
|
|
||||||
this.formDialog.data.display_preferences.dashboard = false
|
|
||||||
this.formDialog.data.display_preferences.dashboard_onchain = false
|
|
||||||
this.formDialog.data.display_preferences.dashboard_mining = false
|
|
||||||
this.formDialog.data.display_preferences.lightning_dashboard = false
|
|
||||||
this.formDialog.data.display_preferences.fun_satoshi_quotes = false
|
|
||||||
this.formDialog.data.display_preferences.mempool_recommended_fees = false
|
|
||||||
this.formDialog.data.display_preferences.onchain = false
|
|
||||||
this.formDialog.data.display_preferences.url_checker = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %} {% block styles %}
|
|
||||||
<style>
|
|
||||||
.col__display_preferences {
|
|
||||||
border: 1px solid red;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
|
@ -1,33 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from fastapi import Depends, Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from lnbits.core.models import User
|
|
||||||
from lnbits.decorators import check_user_exists
|
|
||||||
|
|
||||||
from . import gerty_ext, gerty_renderer
|
|
||||||
from .crud import get_gerty
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/", response_class=HTMLResponse)
|
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
|
||||||
return gerty_renderer().TemplateResponse(
|
|
||||||
"gerty/index.html", {"request": request, "user": user.dict()}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/{gerty_id}", response_class=HTMLResponse)
|
|
||||||
async def display(request: Request, gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
if not gerty:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
|
|
||||||
)
|
|
||||||
return gerty_renderer().TemplateResponse(
|
|
||||||
"gerty/gerty.html", {"request": request, "gerty": gerty_id}
|
|
||||||
)
|
|
|
@ -1,188 +0,0 @@
|
||||||
import json
|
|
||||||
from http import HTTPStatus
|
|
||||||
|
|
||||||
from fastapi import Depends, Query
|
|
||||||
from loguru import logger
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
|
||||||
|
|
||||||
from . import gerty_ext
|
|
||||||
from .crud import (
|
|
||||||
create_gerty,
|
|
||||||
delete_gerty,
|
|
||||||
get_gerty,
|
|
||||||
get_gertys,
|
|
||||||
get_mempool_info,
|
|
||||||
update_gerty,
|
|
||||||
)
|
|
||||||
from .helpers import (
|
|
||||||
gerty_should_sleep,
|
|
||||||
get_next_update_time,
|
|
||||||
get_satoshi,
|
|
||||||
get_screen_data,
|
|
||||||
get_screen_slug_by_index,
|
|
||||||
)
|
|
||||||
from .models import Gerty
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK)
|
|
||||||
async def api_gertys(
|
|
||||||
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
|
|
||||||
):
|
|
||||||
wallet_ids = [wallet.wallet.id]
|
|
||||||
if all_wallets:
|
|
||||||
user = await get_user(wallet.wallet.user)
|
|
||||||
wallet_ids = user.wallet_ids if user else []
|
|
||||||
|
|
||||||
return [gerty.dict() for gerty in await get_gertys(wallet_ids)]
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.post("/api/v1/gerty", status_code=HTTPStatus.CREATED)
|
|
||||||
@gerty_ext.put("/api/v1/gerty/{gerty_id}", status_code=HTTPStatus.OK)
|
|
||||||
async def api_link_create_or_update(
|
|
||||||
data: Gerty,
|
|
||||||
wallet: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
gerty_id: str = Query(None),
|
|
||||||
):
|
|
||||||
if gerty_id:
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
if not gerty:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
if gerty.wallet != wallet.wallet.id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.FORBIDDEN,
|
|
||||||
detail="Come on, seriously, this isn't your Gerty!",
|
|
||||||
)
|
|
||||||
|
|
||||||
data.wallet = wallet.wallet.id
|
|
||||||
gerty = await update_gerty(gerty_id, **data.dict())
|
|
||||||
assert gerty, HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
gerty = await create_gerty(wallet_id=wallet.wallet.id, data=data)
|
|
||||||
|
|
||||||
return {**gerty.dict()}
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.delete("/api/v1/gerty/{gerty_id}")
|
|
||||||
async def api_gerty_delete(
|
|
||||||
gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
|
||||||
):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
|
|
||||||
if not gerty:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
if gerty.wallet != wallet.wallet.id:
|
|
||||||
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Gerty.")
|
|
||||||
|
|
||||||
await delete_gerty(gerty_id)
|
|
||||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
|
|
||||||
async def api_gerty_satoshi():
|
|
||||||
return await get_satoshi()
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/pages/{gerty_id}/{p}")
|
|
||||||
async def api_gerty_json(gerty_id: str, p: int = 0): # page number
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
|
|
||||||
if not gerty:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
display_preferences = json.loads(gerty.display_preferences)
|
|
||||||
|
|
||||||
enabled_screen_count = 0
|
|
||||||
|
|
||||||
enabled_screens = []
|
|
||||||
|
|
||||||
for screen_slug in display_preferences:
|
|
||||||
is_screen_enabled = display_preferences[screen_slug]
|
|
||||||
if is_screen_enabled:
|
|
||||||
enabled_screen_count += 1
|
|
||||||
enabled_screens.append(screen_slug)
|
|
||||||
|
|
||||||
logger.debug("Screens " + str(enabled_screens))
|
|
||||||
data = await get_screen_data(p, enabled_screens, gerty)
|
|
||||||
|
|
||||||
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1
|
|
||||||
|
|
||||||
# get the sleep time
|
|
||||||
sleep_time = gerty.refresh_time if gerty.refresh_time else 300
|
|
||||||
utc_offset = gerty.utc_offset if gerty.utc_offset else 0
|
|
||||||
if gerty_should_sleep(utc_offset):
|
|
||||||
sleep_time_hours = 8
|
|
||||||
sleep_time = 60 * 60 * sleep_time_hours
|
|
||||||
|
|
||||||
return {
|
|
||||||
"settings": {
|
|
||||||
"refreshTime": sleep_time,
|
|
||||||
"requestTimestamp": get_next_update_time(sleep_time, utc_offset),
|
|
||||||
"nextScreenNumber": next_screen_number,
|
|
||||||
"showTextBoundRect": False,
|
|
||||||
"name": gerty.name,
|
|
||||||
},
|
|
||||||
"screen": {
|
|
||||||
"slug": get_screen_slug_by_index(p, enabled_screens),
|
|
||||||
"group": get_screen_slug_by_index(p, enabled_screens),
|
|
||||||
"title": data["title"],
|
|
||||||
"areas": data["areas"],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
###########CACHED MEMPOOL##############
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/fees-recommended/{gerty_id}")
|
|
||||||
async def api_gerty_get_fees_recommended(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("fees_recommended", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/hashrate-1w/{gerty_id}")
|
|
||||||
async def api_gerty_get_hashrate_1w(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("hashrate_1w", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/hashrate-1m/{gerty_id}")
|
|
||||||
async def api_gerty_get_hashrate_1m(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("hashrate_1m", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/statistics/{gerty_id}")
|
|
||||||
async def api_gerty_get_statistics(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("statistics", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/difficulty-adjustment/{gerty_id}")
|
|
||||||
async def api_gerty_get_difficulty_adjustment(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("difficulty_adjustment", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/tip-height/{gerty_id}")
|
|
||||||
async def api_gerty_get_tip_height(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("tip_height", gerty)
|
|
||||||
|
|
||||||
|
|
||||||
@gerty_ext.get("/api/v1/gerty/mempool/{gerty_id}")
|
|
||||||
async def api_gerty_get_mempool(gerty_id):
|
|
||||||
gerty = await get_gerty(gerty_id)
|
|
||||||
return await get_mempool_info("mempool", gerty)
|
|
Loading…
Reference in New Issue
Block a user