Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add icons and highlight successful rebalances #356

Open
wants to merge 10 commits into
base: v1.10.0
Choose a base branch
from
2 changes: 1 addition & 1 deletion gui/static/w3style.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ table {border-radius: 10px} table th{background-color: #f0f0f5;} table tbody tr
th{position:sticky;overflow: hidden;top:0px;z-index:10}tr:first-child th:first-child{border-radius: 10px 0 0 0}tr:first-child th:last-child{border-radius: 0 10px 0 0}tr:last-child td:first-child{border-radius: 0 0 0 10px}tr:last-child td:last-child{border-radius: 0 0 10px 0}
.w3-bordered tr,.w3-table-all tr{border-bottom:1px solid #ddd}.w3-striped tbody tr:nth-child(even){background-color:#f1f1f1}
.w3-table,.w3-table-all{border-spacing:0;width:100%;display:table}.w3-table-all{border:1px solid #ccc}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-hoverable tbody tr:hover,.w3-ul.w3-hoverable li:hover{background-color:#ccc !important}.w3-centered tr th,.w3-centered tr td{text-align:center}
.w3-table td,.w3-table th,.w3-table-all td,.w3-table-all th{padding:8px 8px;display:table-cell;text-align:left;vertical-align:top}
.w3-table th:first-child,.w3-table td:first-child,.w3-table-all th:first-child,.w3-table-all td:first-child{padding-left:16px}
.w3-btn,.w3-button{border:none;display:inline-block;padding:8px 16px;vertical-align:middle;overflow:hidden;text-decoration:none;color:inherit;background-color:inherit;text-align:center;cursor:pointer;white-space:nowrap}
Expand Down
2 changes: 1 addition & 1 deletion gui/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ <h6 style="display: none" id="private_stats">
</tfoot>
</table>
</div>
{% include 'rebalances_table.html' with count=10 load_count=10 title='<a href="/rebalances" target="_blank">Rebalance Requests</a>' %}
{% include 'rebalances_table.html' with target_time=local_settings.3.value count=10 load_count=10 title='<a href="/rebalances" target="_blank">Rebalance Requests</a>' %}
<div id="payments_container" style="display:none" class="w3-container w3-padding-small">
<table id="paymentsTable" class="w3-table-all w3-centered w3-hoverable">
<thead>
Expand Down
8 changes: 4 additions & 4 deletions gui/templates/rebalances.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block title %} {{ block.super }} - Rebalances{% endblock %}
{% block content %}
{% include 'rebalances_table.html' with count=20 status=1 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with count=20 status=0 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with count=20 status=2 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with count=50 load_count=50 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with target_time=target_time count=20 status=1 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with target_time=target_time count=20 status=2 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with target_time=target_time count=20 status=0 load_count=20 title='Rebalance Requests' %}
{% include 'rebalances_table.html' with target_time=target_time count=50 load_count=50 title='Rebalance Requests' %}
{% endblock %}
235 changes: 132 additions & 103 deletions gui/templates/rebalances_table.html
Original file line number Diff line number Diff line change
@@ -1,115 +1,144 @@
{% load humanize %}
<div id="rebalances_div_{{status}}" class="w3-container w3-padding-small">
<h2>{% if status == 2 %}Successful{% elif status == 0 %}Pending{% elif status == 1 %}In-Flight{% else %}Last{% endif %} {{title}}</h2>
<table id="rebalsTable_{{status}}" class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Requested</th>
<th>Start</th>
<th>Stop</th>
<th>Scheduled for</th>
<th>Elapsed</th>
<th>Value</th>
<th>Fee Limit</th>
<th>Target PPM</th>
<th>Fees Paid</th>
<th>Last Hop</th>
<th>Status</th>
<th>Hash</th>
<th>Action</th>
<h2>{% if status == 2 %}Successful{% elif status == 0 %}Pending{% elif status == 1 %}In-Flight{% else %}Last{% endif %} {{title}}</h2>
<table id="rebalsTable_{{status}}" class="w3-table-all w3-centered w3-hoverable">
<tr>
<th>Requested</th>
<th>Start</th>
<th>Stop</th>
<th>Scheduled for</th>
<th>Elapsed</th>
<th>Value</th>
<th>Fee Limit</th>
<th>Target PPM</th>
<th>Fees Paid</th>
<th>Last Hop</th>
<th>Status</th>
<th>Hash</th>
<th>Action</th>
</tr>
<tbody id="rebalances_{{status}}">
</tbody>
<tfoot id="loadMoreRebals_{{status}}">
<tr style="background-color:transparent;cursor:pointer;">
<td colspan="13">
<a onclick='loadRebals_{{status}}()'>Load More</a>
</td>
</tr>
<tbody id="rebalances_{{status}}">
</tbody>
<tfoot id="loadMoreRebals_{{status}}">
<tr style="background-color:transparent;cursor:pointer;">
<td colspan="13">
<a onclick='loadRebals_{{status}}()'>Load More</a>
</td>
</tr>
</tfoot>
</table>
</div>
<script>
function formatDuration(duration){
return `${duration} minute${duration === 1 ? 's':''}`
}
statuses = {0: 'Pending', 1: 'In-Flight', 2: 'Successful', 3: 'Timeout', 4: 'No-Route', 5: 'Error', 6: 'Incorrect-Payment-Details', 7: 'Insufficient-Balance', 400: 'Rebalancer-Request-Failed', 406: 'No-Sources', 408: 'Rebalancer-Request-Timeout', 499: 'Cancelled'}
transformations = {
'requested': rebalance => ({innerHTML: formatDate(rebalance.requested), title: adjustTZ(rebalance.requested)}),
'start': rebalance => ({innerHTML: formatDate(rebalance.start), title: rebalance.start ? adjustTZ(rebalance.start) : '---'}),
'stop': rebalance => ({innerHTML: formatDate(rebalance.stop), title: rebalance.stop ? adjustTZ(rebalance.stop) : '---'}),
'duration': rebalance => ({innerHTML: formatDuration(rebalance.duration)}),
'elapsed': rebalance => ({innerHTML: formatDate(rebalance.start, rebalance.stop).replace(" ago", "")}),
'value': rebalance => ({innerHTML: Number(rebalance.value).toLocaleString()}),
'fee_limit': rebalance => ({innerHTML: Number(rebalance.fee_limit).toLocaleString()}),
'ppm': rebalance => ({innerHTML: Math.round(rebalance.fee_limit*1000000/rebalance.value).toLocaleString()}),
'fees_paid': rebalance => ({innerHTML: rebalance.status == 2 ? Number(rebalance.fees_paid).toLocaleString() : '---'}),
'target_alias': rebalance => ({innerHTML: rebalance.target_alias !== '' ? rebalance.target_alias : '---'}),
'status': rebalance => ({innerHTML: statuses[rebalance.status], title: rebalance.status}),
'payment_hash': rebalance => ({innerHTML: (rebalance.payment_hash || '').length == 0 ? '---' :
`<a href="/route?=${rebalance.payment_hash}" target="_blank">${rebalance.payment_hash.substring(0,7)}</a>`}),
'action': rebalance => {
const input = document.createElement("button")
if (rebalance.status != 0){
input.innerHTML = "🔂"
input.title="Repeat this request"
input.onclick = async function(){
const {value, fee_limit, outgoing_chan_ids, last_hop_pubkey, duration, target_alias, manual} = rebalance
if (last_hop_pubkey.length === 0){
var new_rebal = {value, fee_limit, outgoing_chan_ids, duration, manual}
}else{
var new_rebal = {value, fee_limit, outgoing_chan_ids, last_hop_pubkey, duration, target_alias, manual}
}
const response = await POST('rebalancer', {body: new_rebal})
const table = input.parentElement.parentElement.parentElement
table.prepend(use(transformations).render(response))
}
}
else {
input.innerHTML = "❌"
input.title="Cancel this request"
input.onclick = async function(){
const response = await PUT(`rebalancer/${rebalance.id}`, {body: {status: 499}})

const table = input.parentElement.parentElement.parentElement
const index = input.parentElement.parentElement.rowIndex -1
table.deleteRow(index)
use(transformations).render(response, table.insertRow(index))
</tfoot>
</table>
</div>
<script>
var rapidFire = 'rapidFire', target_time = {{target_time}}
proof-of-reality marked this conversation as resolved.
Show resolved Hide resolved
function formatDuration(duration) {
return `${duration} minute${duration > 1 ? 's':''}`
}
statuses = { 0: 'Pending ⏳', 1: 'In-Flight 🚀', 2: 'Successful ✅', 3: 'Timeout ⏱️', 4: 'No-Route ⭕', 5: 'Error ⛔', 6: 'Incorrect-Payment-Detail❗', 7: 'Insufficient-Balance💸', 400: 'Rebalancer-Failed', 406: 'No-Sources ⛽', 408: 'Rebalancer-Timeout ⏱️', 499: 'Cancelled ✖️' }
template = {
'requested': rebalance => ({ innerHTML: formatDate(rebalance.requested), title: adjustTZ(rebalance.requested) }),
'start': rebalance => ({ innerHTML: formatDate(rebalance.start), title: rebalance.start ? adjustTZ(rebalance.start) : '---' }),
'stop': rebalance => ({ innerHTML: formatDate(rebalance.stop), title: rebalance.stop ? adjustTZ(rebalance.stop) : '---' }),
'duration': rebalance => ({ innerHTML: formatDuration(rebalance.duration) }),
'elapsed': rebalance => ({ innerHTML: formatDate(rebalance.start, rebalance.stop).replace(" ago", "") }),
'value': rebalance => ({ innerHTML: Number(rebalance.value).toLocaleString() }),
'fee_limit': rebalance => ({ innerHTML: Number(rebalance.fee_limit).toLocaleString() }),
'ppm': rebalance => ({ innerHTML: Math.round(rebalance.fee_limit * 1000000 / rebalance.value).toLocaleString() }),
'fees_paid': rebalance => ({ innerHTML: rebalance.status == 2 ? Number(rebalance.fees_paid).toLocaleString() : '---' }),
'target_alias': rebalance => ({ innerHTML: rebalance.target_alias !== '' ? rebalance.target_alias : '---' }),
'status': rebalance => ({ innerHTML: statuses[rebalance.status], title: rebalance.status }),
'payment_hash': rebalance => ({ innerHTML: (rebalance.payment_hash || '').length == 0 ? '---' :
`<a href="/route?=${rebalance.payment_hash}" target="_blank">${rebalance.payment_hash.substring(0, 7)}</a>` }),
'action': rebalance => {
const input = document.createElement("button")
if (rebalance.status != 0) {
input.innerHTML = "🔂"
input.title = "Repeat this request"
input.onclick = async function() {
const { value, fee_limit, outgoing_chan_ids, last_hop_pubkey, duration, target_alias, manual } = rebalance
let new_rebal = { value, fee_limit, outgoing_chan_ids, duration, manual }
if(last_hop_pubkey.length !== 0){
new_rebal['last_hop_pubkey'] = last_hop_pubkey
new_rebal['target_alias'] = target_alias
}
}
return {innerHTML: input}
}
};
async function buildTable(){
const res = await GET('rebalancer', {data: {limit: '{{count}}', status: '{{status}}', payment_hash: '{{payment_hash}}', last_hop_pubkey: '{% if last_hop_pubkey %}{{last_hop_pubkey}}{%else%}{{request.GET.last_hop_pubkey}}{%endif%}' }})
if(res.errors) return
if(res.results.length == 0){
document.getElementById("rebalances_div_{{status}}").innerHTML = `<center><h1>You dont have any ${'{{status}}'=='' ? '' : statuses['{{status}}'].toLowerCase()} rebalance request</h1></center>`
return
const response = await POST('rebalancer', { body: new_rebal })
const table = input.parentElement.parentElement.parentElement
table.prepend(use(template).render(response))
}
}
else {
input.innerHTML = "🛑"
input.title = "Cancel this request"
input.onclick = async function() {
const response = await PUT(`rebalancer/${rebalance.id}`, { body: { status: 499 } })
const table = input.parentElement.parentElement.parentElement
const index = input.parentElement.parentElement.rowIndex - 1
table.deleteRow(index)
use(template).render(response, table.insertRow(index))
}
}
return { innerHTML: input }
}
};
function markRapidFire(tr, index) {
const duration = tr.cells['duration']
duration.innerHTML += ` 🔥`
duration.title = `+${index} RapidFire🔥 rebalances:\nWhen the previous rebalance is successful, LNDg tries new 1min-long rebalances as a lift to quicker fulfill the AR target amount.`
}
async function buildTable(ev) {
const res = await GET('rebalancer', { data: { limit: '{{count}}', status: '{{status}}', payment_hash: '{{payment_hash}}', last_hop_pubkey: '{% if last_hop_pubkey %}{{last_hop_pubkey}}{%else%}{{request.GET.last_hop_pubkey}}{%endif%}' } })
if (res.errors) return
if (res.results.length == 0) {
byId("rebalances_div_{{status}}").innerHTML = `<center><h1>You dont have any ${'{{status}}' == '' ? '' : statuses['{{status}}'].toLowerCase()} rebalance request</h1></center>`
return
}

const table = document.getElementById("rebalances_{{status}}"), template = use(transformations)
table.innerHTML = null
res.results.forEach(rebalance => table.append(template.render(rebalance)));
const table = byId("rebalances_{{status}}"), renderer = use(template)
table.innerHTML = null
for (const rebalance of res.results) {
const tr = renderer.render(rebalance), isRapidFire = rebalance.duration === 1 && wait_period !== 1
table.append(tr)
tr.setAttribute(rapidFire, +isRapidFire)

await auto_refresh(buildTable)
if(rebalance.status == 2) tr.style.backgroundColor = green
if(isRapidFire) continue
var i = tr.rowIndex -2, prev = table.rows[i]
while(prev && parseInt(prev.getAttribute(rapidFire))){
markRapidFire(prev, tr.rowIndex - prev.rowIndex)
prev = table.rows[--i]
}
}

document.addEventListener('DOMContentLoaded', buildTable);
await auto_refresh(buildTable)
}

document.addEventListener('DOMContentLoaded', buildTable);

async function loadRebals_{{status}}(){
const status = "{{ status }}"
const rebalsTable = byId('rebalsTable_'+status)
const lastId = rebalsTable.tBodies[1].lastChild.objId
const rebals_task = GET('rebalancer', {data: {last_hop_pubkey: "{{ last_hop_pubkey }}", id__lt: lastId, status: status, limit: "{{ load_count }}"} })
build_rebals(rebals_task, rebalsTable, status)
async function loadRebals_{{status}}(){
const status = "{{ status }}"
const rebalsTable = byId('rebalsTable_' + status)
const lastId = rebalsTable.tBodies[1].lastChild.objId
const rebals_task = GET('rebalancer', { data: { last_hop_pubkey: "{{ last_hop_pubkey }}", id__lt: lastId, status: status, limit: "{{ load_count }}" } })
build_rebals(rebals_task, rebalsTable, status)
}
async function build_rebals(rebals_task, rebalsTable, status) {
let next_rebals = (await rebals_task).results
if (next_rebals.length == 0) {
byId("loadMoreRebals_" + status).style.display = "none"
return
}
async function build_rebals(rebals_task, rebalsTable, status){
let next_rebals = (await rebals_task).results
if(next_rebals.length==0){
byId("loadMoreRebals_"+status).style.display = "none"
return
}
const tableBody = rebalsTable.tBodies[1]
next_rebals.forEach(rebalance => tableBody.append(use(transformations).render(rebalance)))
const tableBody = rebalsTable.tBodies[1], renderer = use(template)
for (const rebalance of next_rebals) {
const tr = renderer.render(rebalance), isRapidFire = rebalance.duration === 1 && wait_period !== 1
tableBody.append(tr)
tr.setAttribute(rapidFire, +isRapidFire)

if(rebalance.status == 2) tr.style.backgroundColor = green
if(isRapidFire) continue
var i = tr.rowIndex -2, prev = tableBody.rows[i]
while(prev && parseInt(prev.getAttribute(rapidFire))){
markRapidFire(prev, tr.rowIndex - prev.rowIndex)
prev = tableBody.rows[--i]
}
}
</script>
}
</script>
2 changes: 1 addition & 1 deletion gui/templates/rebalancing.html
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,6 @@ <h5 style="float:left" title="Select outgoing channels above">Manual Rebalance R
})
}
</script>
{% include 'rebalances_table.html' with count=20 load_count=10 title='<a href="/rebalances" target="_blank">Rebalance Requests</a>' %}
{% include 'rebalances_table.html' with target_time=local_settings.3.value count=20 load_count=10 title='<a href="/rebalances" target="_blank">Rebalance Requests</a>' %}
{% include 'local_settings.html' with settings=local_settings title='Auto-Rebalancer' %}
{% endblock %}
2 changes: 1 addition & 1 deletion gui/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ def invoices(request):
def rebalances(request):
if request.method == 'GET':
try:
return render(request, 'rebalances.html')
return render(request, 'rebalances.html', {'target_time': LocalSettings.objects.get(key='AR-Time').value})
except Exception as e:
try:
error = str(e.code())
Expand Down