@ -0,0 +1 @@
|
||||
from lws.factory import create_app
|
@ -0,0 +1,80 @@
|
||||
from quart import Blueprint, render_template, request
|
||||
from monero.seed import Seed
|
||||
from quart_auth import login_required
|
||||
|
||||
from lws.models import User, Wallet
|
||||
from lws.helpers import lws
|
||||
from lws import config
|
||||
|
||||
bp = Blueprint('htmx', 'htmx', url_prefix="/htmx")
|
||||
|
||||
|
||||
@bp.route("/create_wallet")
|
||||
async def create_wallet():
|
||||
"""Creating a new wallet with newly generated seeds"""
|
||||
seed = Seed()
|
||||
return await render_template(
|
||||
"htmx/create_wallet.html",
|
||||
seed=seed.phrase,
|
||||
address=seed.public_address(),
|
||||
psk=seed.public_spend_key(),
|
||||
pvk=seed.public_view_key(),
|
||||
ssk=seed.secret_spend_key(),
|
||||
svk=seed.secret_view_key()
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/import_wallet")
|
||||
async def import_wallet():
|
||||
"""Importing an existing wallet"""
|
||||
return await render_template("htmx/import_wallet.html")
|
||||
|
||||
|
||||
@bp.route("/label_wallet")
|
||||
async def label_wallet():
|
||||
"""Changing the label on a stored wallet"""
|
||||
address = request.args.get("address")
|
||||
label = request.args.get("label")
|
||||
return await render_template(
|
||||
"htmx/label_wallet.html",
|
||||
address=address,
|
||||
label=label
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/set_height")
|
||||
async def set_height():
|
||||
"""Setting a new height to scan from"""
|
||||
address = request.args.get("address")
|
||||
height = request.args.get("height")
|
||||
return await render_template(
|
||||
"htmx/set_height.html",
|
||||
address=address,
|
||||
height=height
|
||||
)
|
||||
|
||||
|
||||
@bp.route("/show_wallets")
|
||||
@login_required
|
||||
async def show_wallets():
|
||||
"""Showing all wallets in the database in a table"""
|
||||
admin = User.select().first()
|
||||
lws.init(admin.view_key)
|
||||
accounts = lws.list_accounts()
|
||||
if 'hidden' in accounts:
|
||||
del accounts["hidden"]
|
||||
# save wallets if they don't exist in the db
|
||||
for status in accounts:
|
||||
for account in accounts[status]:
|
||||
w = Wallet.select().where(Wallet.address == account["address"]).first()
|
||||
if not w:
|
||||
w = Wallet(
|
||||
address=account["address"]
|
||||
)
|
||||
w.save()
|
||||
requests = lws.list_requests()
|
||||
return await render_template(
|
||||
"htmx/show_wallets.html",
|
||||
accounts=accounts,
|
||||
requests=requests
|
||||
)
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,26 @@
|
||||
<div id="create_wallet">
|
||||
<form hx-target="#show_wallets" hx-post="{{ url_for('wallet.add') }}" action="#">
|
||||
<label for="label">Label</label>
|
||||
<input type="text" name="label" />
|
||||
<p>Seed: <span class="key">{{ seed }}</span></p>
|
||||
<p>Public Address: <span class="key">{{ address }}</span></p>
|
||||
<p>Public Spend Key: <span class="key">{{ psk }}</span></p>
|
||||
<p>Public View Key: <span class="key">{{ pvk }}</span></p>
|
||||
<p>Secret Spend Key: <span class="key">{{ ssk }}</span></p>
|
||||
<p>Secret View Key: <span class="key">{{ svk }}</span></p>
|
||||
<input type="text" name="address" value="{{ address }}" class="hidden" />
|
||||
<input type="text" name="view_key" value="{{ svk }}" class="hidden" />
|
||||
<input type="number" name="restore_height" value="-1" class="hidden" />
|
||||
<button type="submit">Create</button>
|
||||
<button onclick="cancelCreate()">Cancel</button>
|
||||
</form>
|
||||
<script>
|
||||
function cancelCreate() {
|
||||
document.getElementById("create_wallet").innerHTML = "";
|
||||
}
|
||||
|
||||
document.getElementById("create_wallet").addEventListener("submit", function() {
|
||||
cancelCreate();
|
||||
})
|
||||
</script>
|
||||
</div>
|
@ -0,0 +1,22 @@
|
||||
<div id="import_wallet">
|
||||
<form hx-target="#show_wallets" hx-post="{{ url_for('wallet.add') }}" action="#" >
|
||||
<label for="label">Label</label>
|
||||
<input type="text" name="label" />
|
||||
<label for="seed">Mnemonic Seed</label>
|
||||
<p class="subtext">12, 13, or 25 word seeds</p>
|
||||
<input type="password" name="seed" />
|
||||
<label for="restore_height">Restore Height</label>
|
||||
<input type="number" name="restore_height" value="-1" />
|
||||
<button type="submit">Import</button>
|
||||
<button onclick="cancelImport()">Cancel</button>
|
||||
</form>
|
||||
<script>
|
||||
function cancelImport() {
|
||||
document.getElementById("import_wallet").innerHTML = "";
|
||||
}
|
||||
|
||||
document.getElementById("import_wallet").addEventListener("submit", function() {
|
||||
cancelImport();
|
||||
})
|
||||
</script>
|
||||
</div>
|
@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<form action="#" onsubmit="updateLabel(event)">
|
||||
<input type="text" name="label" onkeyup="updateLabel(event)" value="{{ label }}" id="label-input-{{ address }}" onfocusout="updateLabel(event)">
|
||||
</form>
|
||||
<script>
|
||||
function updateLabel(e) {
|
||||
e.preventDefault();
|
||||
if (e.keyCode === 13) {
|
||||
htmx.ajax('GET', `/wallet/{{ address }}/label/${e.target.value}`, '#show_wallets');
|
||||
} else if (e.keyCode === 27 || e.type == "focusout") {
|
||||
htmx.ajax('GET', '{{ url_for("htmx.show_wallets") }}', '#show_wallets');
|
||||
}
|
||||
}
|
||||
document.getElementById('label-input-{{ address }}').focus();
|
||||
</script>
|
||||
</div>
|
@ -0,0 +1,16 @@
|
||||
<div>
|
||||
<form action="#" onsubmit="updateHeight(event)">
|
||||
<input type="text" name="label" onkeyup="updateHeight(event)" value="{{ height }}" id="height-input-{{ address }}" onfocusout="updateHeight(event)">
|
||||
</form>
|
||||
<script>
|
||||
function updateHeight(e) {
|
||||
e.preventDefault();
|
||||
if (e.keyCode === 13) {
|
||||
htmx.ajax('GET', `/wallet/{{ address }}/rescan/${e.target.value}`, '#show_wallets');
|
||||
} else if (e.keyCode === 27 || e.type == "focusout") {
|
||||
htmx.ajax('GET', '{{ url_for("htmx.show_wallets") }}', '#show_wallets');
|
||||
}
|
||||
}
|
||||
document.getElementById('height-input-{{ address }}').focus();
|
||||
</script>
|
||||
</div>
|
@ -0,0 +1,63 @@
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Label</th>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
<th>Address</th>
|
||||
<th>Height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for status in requests %}
|
||||
{% for request in requests[status] %}
|
||||
<tr>
|
||||
<td>?</td>
|
||||
<td>
|
||||
<span class="tag text-grey">
|
||||
PENDING
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="button primary" hx-target="#show_wallets" hx-get="{{ url_for('wallet.accept', address=request['address']) }}" hx-swap="innerHTML">Accept</button>
|
||||
<button class="button error" hx-target="#show_wallets" hx-get="{{ url_for('wallet.reject', address=request['address']) }}" hx-swap="innerHTML">Reject</button>
|
||||
</td>
|
||||
<td>{{ request['address'] | shorten }}</td>
|
||||
<td>{{ request['start_height'] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for status in accounts %}
|
||||
{% for account in accounts[status] %}
|
||||
<tr>
|
||||
<td>
|
||||
<div hx-get="/htmx/label_wallet" hx-target="this" hx-swap="outerHTML" hx-vals='{"address": "{{ account['address'] }}", "label": "{{ account['address'] | find_label }}"}'>
|
||||
<span class="underline">{{ account['address'] | find_label }}</span> <i class="fa-regular fa-pen-to-square"></i>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="tag {% if status == 'active' %}text-primary{% else %}text-error{% endif %}">
|
||||
{{ status | capitalize }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if status == 'active' %}
|
||||
<button class="button dark outline" hx-target="#show_wallets" hx-get="{{ url_for('wallet.modify', address=account['address'], status='inactive') }}" hx-swap="innerHTML">Deactivate</button>
|
||||
{% else %}
|
||||
<button class="button primary outline" hx-target="#show_wallets" hx-get="{{ url_for('wallet.modify', address=account['address'], status='active') }}" hx-swap="innerHTML" >Activate</button>
|
||||
<button class="button error" hx-target="#show_wallets" hx-get="{{ url_for('wallet.modify', address=account['address'], status='hidden') }}" hx-swap="innerHTML" >Hide</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ account['address'] | shorten }}</td>
|
||||
<td>
|
||||
<div hx-get="/htmx/set_height" hx-target="this" hx-swap="outerHTML" hx-vals='{"address": "{{ account['address'] }}", "height": "{{ account['scan_height'] }}"}'>
|
||||
<span class="underline">{{ account['scan_height'] }}</span>
|
||||
<i class="fa-regular fa-pen-to-square"></i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -1,68 +1,43 @@
|
||||
{% extends 'includes/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>LWS Web Admin</h1>
|
||||
|
||||
<h1>Monero Lightwallet Server</h1>
|
||||
<p>LWS Admin: {{ config.LWS_ADMIN_URL }}</p>
|
||||
<p>LWS RPC: {{ config.LWS_URL }}</p>
|
||||
<h3>Accounts</h3>
|
||||
<a href="/wallet/add" class="button outline primary">Add Wallet</a>
|
||||
<table class="striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<th>Action</th>
|
||||
<th>Address</th>
|
||||
<th>Height</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for status in accounts %}
|
||||
{% for account in accounts[status] %}
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tag text-white {% if status == 'active' %}bg-success{% else %}bg-error{% endif %}">
|
||||
{{ status | upper }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if status == 'active' %}
|
||||
<a href="/wallet/{{ account['address'] }}/disable" class="">disable</a>
|
||||
{% else %}
|
||||
<a href="/wallet/{{ account['address'] }}/enable" class="">enable</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ account['address'] | shorten }}</td>
|
||||
<td>
|
||||
|
||||
<form method="get" action="{{ url_for('wallet.rescan') }}">
|
||||
{{ account['scan_height'] }}
|
||||
<input type="integer" name="height" style="width: 5em; display: inline;" />
|
||||
<input type="hidden" name="address" value="{{ account['address'] }}" />
|
||||
<button type="submit">Rescan</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<p>Monero Web Wallet: <a href="{{ config.MYMONERO_URL }}" target="_blank">{{ config.MYMONERO_URL }}</a></p>
|
||||
|
||||
<div>
|
||||
<a hx-get="/htmx/import_wallet" hx-target="#walletForm" class="button primary outline">Import Wallet</a>
|
||||
<a hx-get="/htmx/create_wallet" hx-target="#walletForm" class="button primary">Create Wallet</a>
|
||||
<p id="walletForm" style="margin: 2em 0 2em 0"></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>Accounts</h3>
|
||||
<p>
|
||||
The below Monero accounts are stored in LWS;
|
||||
only active accounts will be synced.
|
||||
Accounts created in the web wallet must be approved here before use.
|
||||
</p>
|
||||
<div hx-get="/htmx/show_wallets" hx-target="#show_wallets" class="button outline" hx-indicator="#refresh_loader" onclick="handleRefresh()">
|
||||
Refresh
|
||||
<i class="fa-solid fa-rotate-right indicator" id="refresh_loader"></i>
|
||||
</div>
|
||||
<div hx-trigger="load" hx-get="/htmx/show_wallets" id="show_wallets" onload=""></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function handleRefresh() {
|
||||
const show_wallets = document.getElementById('show_wallets');
|
||||
const loader = document.getElementById('refresh_loader');
|
||||
loader.classList.add('fa-spin');
|
||||
show_wallets.classList.add('showFade');
|
||||
setTimeout(function() {
|
||||
show_wallets.classList.remove('showFade');
|
||||
loader.classList.remove('fa-spin');
|
||||
}, 2000)
|
||||
}
|
||||
</script>
|
||||
|
||||
{% for status in requests %}
|
||||
{% for request in requests[status] %}
|
||||
<tr>
|
||||
<td>
|
||||
<span class="tag text-white bg-dark">
|
||||
PENDING
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/wallet/{{ request['address'] }}/accept" class="">approve</a> |
|
||||
<a href="/wallet/{{ request['address'] }}/reject" class="">reject</a>
|
||||
</td>
|
||||
<td>{{ request['address'] | shorten }}</td>
|
||||
<td>{{ request['start_height'] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,15 @@
|
||||
{% extends 'includes/base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<a href="/">Go Back</a>
|
||||
<h1>Add a Wallet</h1>
|
||||
<form method="post">
|
||||
<label for="address">Address</label>
|
||||
<input type="text" name="address" value="{{ request.args.get('address', '') }}" />
|
||||
<label for="view_key">View Key</label>
|
||||
<input type="text" name="view_key" />
|
||||
<label for="restore_height">Restore Height</label>
|
||||
<input type="number" name="restore_height" />
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue