mirror of
https://github.com/cryptosharks131/lndg.git
synced 2026-08-13 12:33:04 +02:00
v1.3.0 (#120)
This commit is contained in:
parent
53e58586f3
commit
c63c065c6c
29 changed files with 1382 additions and 330 deletions
46
.github/workflows/on-push-github.yml
vendored
Normal file
46
.github/workflows/on-push-github.yml
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
name: Build on push
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build image
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set env variables
|
||||
run: |
|
||||
echo "BRANCH=$(echo ${GITHUB_REF#refs/heads/} | sed 's/\//-/g')" >> $GITHUB_ENV
|
||||
IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
|
||||
echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
id: qemu
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@v2
|
||||
id: buildx
|
||||
|
||||
- name: Run Docker buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:latest \
|
||||
--output "type=registry" ./
|
||||
47
.github/workflows/on-tag-github.yml
vendored
Normal file
47
.github/workflows/on-tag-github.yml
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
name: Build on tag
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v[0-9]+.[0-9]+.[0-9]+
|
||||
- v[0-9]+.[0-9]+.[0-9]+-*
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build image
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- name: Checkout project
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set env variables
|
||||
run: |
|
||||
echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
|
||||
IMAGE_NAME="${GITHUB_REPOSITORY#*/}"
|
||||
echo "IMAGE_NAME=${IMAGE_NAME//docker-/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
id: qemu
|
||||
|
||||
- name: Setup Docker buildx action
|
||||
uses: docker/setup-buildx-action@v2
|
||||
id: buildx
|
||||
|
||||
- name: Run Docker buildx
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--tag ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME:$TAG \
|
||||
--output "type=registry" ./
|
||||
45
README.md
45
README.md
|
|
@ -1,8 +1,9 @@
|
|||
# LNDg
|
||||
Lite GUI web interface to analyze lnd data and manage your node with automation.
|
||||
|
||||
Start by choosing one of the following installation methods:
|
||||
[Docker Installation](https://github.com/cryptosharks131/lndg#docker-installation-requires-docker-and-docker-compose-be-installed) | [Umbrel Installation](https://github.com/cryptosharks131/lndg#umbrel-installation) | [Manual Installation](https://github.com/cryptosharks131/lndg#manual-installation)
|
||||
Start by choosing one of the following installation methods: [Docker Installation](https://github.com/cryptosharks131/lndg#docker-installation-requires-docker-and-docker-compose-be-installed) | [Manual Installation](https://github.com/cryptosharks131/lndg#manual-installation)
|
||||
|
||||
LNDg can also be found directly on popular apps like Umbrel and Citadel with a 1-click install from the GUI.
|
||||
|
||||
## Docker Installation (requires docker and docker-compose be installed)
|
||||
### Build and deploy
|
||||
|
|
@ -37,46 +38,6 @@ docker-compose up -d
|
|||
docker system prune -f
|
||||
```
|
||||
|
||||
## Manual Umbrel Installation (now available directly from the Umbrel app store)
|
||||
|
||||
### Build and deploy
|
||||
1. Log into your umbrel via ssh
|
||||
2. Clone respository `git clone https://github.com/cryptosharks131/lndg.git`
|
||||
3. Change directory `cd lndg`
|
||||
4. Copy and replace the contents of the `docker-compose.yaml` with the below: `nano docker-compose.yaml`
|
||||
```
|
||||
services:
|
||||
lndg:
|
||||
build: .
|
||||
volumes:
|
||||
- /home/umbrel/umbrel/lnd:/root/.lnd:ro
|
||||
- /home/umbrel/lndg/data:/lndg/data:rw
|
||||
command:
|
||||
- sh
|
||||
- -c
|
||||
- python initialize.py -net 'mainnet' -server '10.21.21.9:10009' -d && supervisord && python manage.py runserver 0.0.0.0:8000
|
||||
ports:
|
||||
- 8889:8000
|
||||
networks:
|
||||
default:
|
||||
external: true
|
||||
name: umbrel_main_network
|
||||
```
|
||||
5. Deploy your docker image: `docker-compose up -d`
|
||||
6. You can now access LNDg via your browser on port 8889: `http://umbrel.local:8889`
|
||||
7. Open and copy the password from output file: `nano data/lndg-admin.txt`
|
||||
8. Use the password from the output file and the username `lndg-admin` to login
|
||||
|
||||
### Updating
|
||||
```
|
||||
docker-compose down
|
||||
docker-compose build --no-cache
|
||||
docker-compose up -d
|
||||
|
||||
# OPTIONAL: remove unused builds and objects
|
||||
docker system prune -f
|
||||
```
|
||||
|
||||
## Manual Installation
|
||||
### Step 1 - Install lndg
|
||||
1. Clone respository `git clone https://github.com/cryptosharks131/lndg.git`
|
||||
|
|
|
|||
12
gui/forms.py
12
gui/forms.py
|
|
@ -80,10 +80,13 @@ class AutoRebalanceForm(forms.Form):
|
|||
target_time = forms.IntegerField(label='target_time', required=False)
|
||||
fee_rate = forms.IntegerField(label='fee_rate', required=False)
|
||||
outbound_percent = forms.FloatField(label='outbound_percent', required=False)
|
||||
inbound_percent = forms.FloatField(label='inbound_percent', required=False)
|
||||
max_cost = forms.FloatField(label='max_cost', required=False)
|
||||
variance = forms.IntegerField(label='variance', required=False)
|
||||
wait_period = forms.IntegerField(label='wait_period', required=False)
|
||||
autopilot = forms.IntegerField(label='autopilot', required=False)
|
||||
autopilotdays = forms.IntegerField(label='autopilotdays', required=False)
|
||||
targetallchannels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
|
||||
|
||||
updates_channel_codes = [
|
||||
(0, 'base_fee'),
|
||||
|
|
@ -96,6 +99,7 @@ updates_channel_codes = [
|
|||
(7, 'channel_state'),
|
||||
(8, 'auto_fees'),
|
||||
(9, 'cltv'),
|
||||
(10, 'closing_costs'),
|
||||
]
|
||||
|
||||
class UpdateChannel(forms.Form):
|
||||
|
|
@ -103,6 +107,12 @@ class UpdateChannel(forms.Form):
|
|||
target = forms.IntegerField(label='target')
|
||||
update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
|
||||
|
||||
class UpdatePending(forms.Form):
|
||||
funding_txid = forms.CharField(label='funding_txid', max_length=64)
|
||||
output_index = forms.IntegerField(label='output_index')
|
||||
target = forms.IntegerField(label='target')
|
||||
update_target = forms.ChoiceField(label='update_target', choices=updates_channel_codes)
|
||||
|
||||
class UpdateSetting(forms.Form):
|
||||
key = forms.CharField(label='setting', max_length=20)
|
||||
value = forms.CharField(label='value', max_length=50)
|
||||
|
|
@ -128,4 +138,4 @@ class BatchOpenForm(forms.Form):
|
|||
amt9 = forms.IntegerField(label='amt9', required=False)
|
||||
pubkey10 = forms.CharField(label='pubkey10', max_length=66, required=False)
|
||||
amt10 = forms.IntegerField(label='amt10', required=False)
|
||||
fee_rate = forms.IntegerField(label='fee_rate')
|
||||
fee_rate = forms.IntegerField(label='fee_rate')
|
||||
|
|
|
|||
70
gui/migrations/0030_auto_20220722_0912.py
Normal file
70
gui/migrations/0030_auto_20220722_0912.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Generated by Django 3.2.7 on 2022-07-21 05:57
|
||||
|
||||
from django.db import migrations, models
|
||||
from requests import get
|
||||
from lndg.settings import LND_NETWORK
|
||||
|
||||
def update_close_fees(apps, schedma_editor):
|
||||
channels = apps.get_model('gui', 'channels')
|
||||
closures = apps.get_model('gui', 'closures')
|
||||
resolutions = apps.get_model('gui', 'resolutions')
|
||||
settings = apps.get_model('gui', 'localsettings')
|
||||
def network_links():
|
||||
if settings.objects.filter(key='GUI-NetLinks').exists():
|
||||
network_links = str(settings.objects.filter(key='GUI-NetLinks')[0].value)
|
||||
else:
|
||||
network_links = 'https://mempool.space'
|
||||
return network_links
|
||||
def get_tx_fees(txid):
|
||||
base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
|
||||
try:
|
||||
request_data = get(base_url + txid).json()
|
||||
fee = request_data['fee']
|
||||
except Exception as e:
|
||||
print('Error getting closure fees for', txid, '-', str(e))
|
||||
fee = 0
|
||||
return fee
|
||||
try:
|
||||
for closure in closures.objects.exclude(open_initiator=2, close_type=0):
|
||||
if channels.objects.filter(chan_id=closure.chan_id).exists():
|
||||
channel = channels.objects.filter(chan_id=closure.chan_id)[0]
|
||||
closing_costs = get_tx_fees(closure.closing_tx) if closure.open_initiator == 1 else 0
|
||||
for resolution in resolutions.objects.filter(chan_id=closure.chan_id).exclude(resolution_type=2):
|
||||
closing_costs += get_tx_fees(resolution.sweep_txid)
|
||||
channel.closing_costs = closing_costs
|
||||
channel.save()
|
||||
except Exception as e:
|
||||
print('Migration step failed:', str(e))
|
||||
|
||||
def revert_close_fees(apps, schedma_editor):
|
||||
pass
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gui', '0029_update_percent_vars'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='channels',
|
||||
name='closing_costs',
|
||||
field=models.IntegerField(default=0),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rebalancer',
|
||||
name='fees_paid',
|
||||
field=models.FloatField(default=None, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='channels',
|
||||
name='ar_in_target',
|
||||
field=models.IntegerField(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='channels',
|
||||
name='auto_fees',
|
||||
field=models.BooleanField(),
|
||||
),
|
||||
migrations.RunPython(update_close_fees, revert_close_fees),
|
||||
]
|
||||
33
gui/migrations/0031_pendingchannels.py
Normal file
33
gui/migrations/0031_pendingchannels.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 3.2.7 on 2022-08-05 11:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gui', '0030_auto_20220722_0912'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PendingChannels',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('funding_txid', models.CharField(max_length=64)),
|
||||
('output_index', models.IntegerField()),
|
||||
('local_base_fee', models.IntegerField(default=None, null=True)),
|
||||
('local_fee_rate', models.IntegerField(default=None, null=True)),
|
||||
('local_cltv', models.IntegerField(default=None, null=True)),
|
||||
('auto_rebalance', models.BooleanField(default=None, null=True)),
|
||||
('ar_amt_target', models.BigIntegerField(default=None, null=True)),
|
||||
('ar_in_target', models.IntegerField(default=None, null=True)),
|
||||
('ar_out_target', models.IntegerField(default=None, null=True)),
|
||||
('ar_max_cost', models.IntegerField(default=None, null=True)),
|
||||
('auto_fees', models.BooleanField(default=None, null=True)),
|
||||
],
|
||||
options={
|
||||
'unique_together': {('funding_txid', 'output_index')},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
|
@ -95,13 +95,21 @@ class Channels(models.Model):
|
|||
last_update = models.DateTimeField()
|
||||
auto_rebalance = models.BooleanField(default=False)
|
||||
ar_amt_target = models.BigIntegerField()
|
||||
ar_in_target = models.IntegerField(default=100)
|
||||
ar_in_target = models.IntegerField()
|
||||
ar_out_target = models.IntegerField()
|
||||
ar_max_cost = models.IntegerField()
|
||||
fees_updated = models.DateTimeField(default=timezone.now)
|
||||
auto_fees = models.BooleanField(default=False)
|
||||
auto_fees = models.BooleanField()
|
||||
closing_costs = models.IntegerField(default=0)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.auto_fees is None:
|
||||
if LocalSettings.objects.filter(key='AF-Enabled').exists():
|
||||
enabled = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value)
|
||||
else:
|
||||
LocalSettings(key='AF-Enabled', value='0').save()
|
||||
enabled = 0
|
||||
self.auto_fees = False if enabled == 0 else True
|
||||
if not self.ar_out_target:
|
||||
if LocalSettings.objects.filter(key='AR-Outbound%').exists():
|
||||
outbound_setting = int(LocalSettings.objects.filter(key='AR-Outbound%')[0].value)
|
||||
|
|
@ -109,6 +117,13 @@ class Channels(models.Model):
|
|||
LocalSettings(key='AR-Outbound%', value='75').save()
|
||||
outbound_setting = 75
|
||||
self.ar_out_target = outbound_setting
|
||||
if not self.ar_in_target:
|
||||
if LocalSettings.objects.filter(key='AR-Inbound%').exists():
|
||||
inbound_setting = int(LocalSettings.objects.filter(key='AR-Inbound%')[0].value)
|
||||
else:
|
||||
LocalSettings(key='AR-Inbound%', value='100').save()
|
||||
inbound_setting = 100
|
||||
self.ar_in_target = inbound_setting
|
||||
if not self.ar_amt_target:
|
||||
if LocalSettings.objects.filter(key='AR-Target%').exists():
|
||||
amt_setting = float(LocalSettings.objects.filter(key='AR-Target%')[0].value)
|
||||
|
|
@ -153,6 +168,7 @@ class Rebalancer(models.Model):
|
|||
status = models.IntegerField(default=0)
|
||||
payment_hash = models.CharField(max_length=64, null=True, default=None)
|
||||
manual = models.BooleanField(default=False)
|
||||
fees_paid = models.FloatField(null=True, default=None)
|
||||
class Meta:
|
||||
app_label = 'gui'
|
||||
|
||||
|
|
@ -247,4 +263,20 @@ class Autofees(models.Model):
|
|||
old_value = models.IntegerField()
|
||||
new_value = models.IntegerField()
|
||||
class Meta:
|
||||
app_label = 'gui'
|
||||
app_label = 'gui'
|
||||
|
||||
class PendingChannels(models.Model):
|
||||
funding_txid = models.CharField(max_length=64)
|
||||
output_index = models.IntegerField()
|
||||
local_base_fee = models.IntegerField(null=True, default=None)
|
||||
local_fee_rate = models.IntegerField(null=True, default=None)
|
||||
local_cltv = models.IntegerField(null=True, default=None)
|
||||
auto_rebalance = models.BooleanField(null=True, default=None)
|
||||
ar_amt_target = models.BigIntegerField(null=True, default=None)
|
||||
ar_in_target = models.IntegerField(null=True, default=None)
|
||||
ar_out_target = models.IntegerField(null=True, default=None)
|
||||
ar_max_cost = models.IntegerField(null=True, default=None)
|
||||
auto_fees = models.BooleanField(null=True, default=None)
|
||||
class Meta:
|
||||
app_label = 'gui'
|
||||
unique_together = (('funding_txid', 'output_index'),)
|
||||
|
|
@ -204,11 +204,11 @@
|
|||
{% csrf_token %}
|
||||
{% if settings.key == 'AR-Target%' %}
|
||||
<input style="text-align:center" id="value" type="number" step="0.1" min="0.1" max="100" name="value" value="{{ settings.value }}">
|
||||
{% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod'%}
|
||||
{% elif settings.key|slice:"-1:" == '%' or settings.key == 'AR-Variance' or settings.key == 'AF-Increment' or settings.key == 'AF-Multiplier' or settings.key == 'AF-FailedHTLCs' or settings.key == 'AR-WaitPeriod' or settings.key == 'AR-APDays' %}
|
||||
<input style="text-align:center" id="value" type="number" min="1" max="100" name="value" value="{{ settings.value }}">
|
||||
{% elif settings.key == 'AR-Time' %}
|
||||
<input style="text-align:center" id="value" type="number" min="1" max="60" name="value" value="{{ settings.value }}">
|
||||
{% elif settings.key == 'AR-MaxFeeRate' or settings.key == 'AF-MaxRate' %}
|
||||
{% elif settings.key == 'AR-MaxFeeRate' %}
|
||||
<input style="text-align:center" id="value" type="number" min="1" max="2500" name="value" value="{{ settings.value }}">
|
||||
{% elif settings.key == 'AF-MaxRate' or settings.key == 'AF-MinRate' %}
|
||||
<input style="text-align:center" id="value" type="number" min="0" max="5000" name="value" value="{{ settings.value }}">
|
||||
|
|
@ -227,4 +227,4 @@
|
|||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
<footer>
|
||||
<div id="footer">
|
||||
<div class="w3-container w3-padding-small">
|
||||
<center>LNDg v1.2.1</center>
|
||||
<center>LNDg v1.3.0</center>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -217,9 +217,9 @@
|
|||
<td>{{ forward.amt_out|intcomma }}</td>
|
||||
<td>{% if forward.chan_in_alias == '' %}---{% else %}{{ forward.chan_in_alias }}{% endif %}</td>
|
||||
<td>{% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %}</td>
|
||||
<td>{{ forward.chan_id_in }}</td>
|
||||
<td>{{ forward.chan_id_out }}</td>
|
||||
<td>{{ forward.fee }}</td>
|
||||
<td><a href="/channel?={{ forward.chan_id_in }}" target="_blank">{{ forward.chan_id_in }}</a></td>
|
||||
<td><a href="/channel?={{ forward.chan_id_out }}" target="_blank">{{ forward.chan_id_out }}</a></td>
|
||||
<td>{{ forward.fee|intcomma }}</td>
|
||||
<td>{{ forward.ppm|intcomma }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -235,21 +235,25 @@
|
|||
<th>Start</th>
|
||||
<th>Stop</th>
|
||||
<th>Scheduled Duration</th>
|
||||
<th>Actual Duration</th>
|
||||
<th>Value</th>
|
||||
<th>Fee Limit</th>
|
||||
<th>Target PPM</th>
|
||||
<th>Fees Paid</th>
|
||||
<th>Last Hop Alias</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{% for rebalance in rebalances %}
|
||||
<tr>
|
||||
<td title="{{ rebalance.requested }}">{{ rebalance.requested|naturaltime }}</td>
|
||||
<td {% if rebalance.status == 0 %}>N/A{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td {% if rebalance.status > 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>N/A{% endif %}</td>
|
||||
<td {% if rebalance.status == 0 %}>---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td {% if rebalance.status > 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}</td>
|
||||
<td>{{ rebalance.duration }} minutes</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}</td>
|
||||
<td>{{ rebalance.value|intcomma }}</td>
|
||||
<td>{{ rebalance.fee_limit|intcomma }}</td>
|
||||
<td>{{ rebalance.ppm|intcomma }}</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}</td>
|
||||
<td title="{{ rebalance.status }}">{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}<a href="/route?={{ rebalance.payment_hash }}" target="_blank">Successful</a>{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}</td>
|
||||
</tr>
|
||||
|
|
@ -281,9 +285,9 @@
|
|||
<td>{{ payment.fee|intcomma }}</td>
|
||||
<td>{{ payment.ppm|intcomma }}</td>
|
||||
<td>{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{{ payment.chan_out }}{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ payment.message }}">{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -308,13 +312,13 @@
|
|||
{% for invoice in invoices %}
|
||||
<tr>
|
||||
<td title="{{ invoice.creation_date }}">{{ invoice.creation_date|naturaltime }}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}</td>
|
||||
<td>{{ invoice.r_hash }}</td>
|
||||
<td>{{ invoice.value|add:"0"|intcomma }}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ invoice.message }}">{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -362,4 +366,4 @@
|
|||
<center><h1>No data found for this channel!</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,105 @@
|
|||
{% block title %} {{ block.super }} - Closures{% endblock %}
|
||||
{% block content %}
|
||||
{% load humanize %}
|
||||
{% if pending_closed %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Pending Close Channels</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Balance In Limbo</th>
|
||||
<th>Local Commit Fees</th>
|
||||
<th width=20%>Closing TX</th>
|
||||
</tr>
|
||||
{% for channel in pending_closed %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.limbo_balance|intcomma }}</td>
|
||||
<td>{{ channel.local_commit_fee_sat }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if pending_force_closed %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Pending Force Close Channels</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Balance In Limbo</th>
|
||||
<th>Maturity</th>
|
||||
<th width=20%>Closing TX</th>
|
||||
</tr>
|
||||
{% for channel in pending_force_closed %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.limbo_balance|intcomma }}</td>
|
||||
<td title="Blocks: {{ channel.blocks_til_maturity|intcomma }}">{{ channel.maturity_datetime|naturaltime }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if waiting_for_close %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Channels Waiting To Close</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Balance In Limbo</th>
|
||||
<th>Local Commit Fee</th>
|
||||
<th width=20%>Closing TX</th>
|
||||
</tr>
|
||||
{% for channel in waiting_for_close %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.limbo_balance|intcomma }}</td>
|
||||
<td>{{ channel.local_commit_fee_sat }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if closures %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Closures</h2>
|
||||
|
|
@ -18,6 +117,7 @@
|
|||
<th>Opener</th>
|
||||
<th>Closer</th>
|
||||
<th>Resolutions</th>
|
||||
<th>Costs</th>
|
||||
</tr>
|
||||
{% for closure in closures %}
|
||||
<tr>
|
||||
|
|
@ -32,6 +132,20 @@
|
|||
<td>{% if closure.open_initiator == 0 %}Unknown{% elif closure.open_initiator == 1 %}Local{% elif closure.open_initiator == 2 %}Remote{% elif closure.open_initiator == 3 %}Both{% else %}{{ closure.open_initiator }}{% endif %}</td>
|
||||
<td>{% if closure.close_initiator == 0 %}Unknown{% elif closure.close_initiator == 1 %}Local{% elif closure.close_initiator == 2 %}Remote{% elif closure.close_initiator == 3 %}Both{% else %}{{ closure.close_initiator }}{% endif %}</td>
|
||||
<td>{% if closure.resolution_count > 0 %}<a href="/resolutions?={{ closure.chan_id }}" target="_blank">Details</a>{% else %}---{% endif %}</td>
|
||||
<td>
|
||||
{% if closure.closing_costs == '' %}
|
||||
---
|
||||
{% elif closure.open_initiator == 2 and closure.close_type == 0 %}
|
||||
---
|
||||
{% else %}
|
||||
<form action="/update_channel/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="0" max="1000000" name="target" value="{{ closure.closing_costs|add:"0" }}">
|
||||
<input type="hidden" name="chan_id" value="{{ closure.chan_id }}">
|
||||
<input type="hidden" name="update_target" value="10">
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -42,4 +156,4 @@
|
|||
<center><h1>No channel closures found!</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
{% for failed_htlc in failed_htlcs %}
|
||||
<tr>
|
||||
<td title="{{ failed_htlc.timestamp }}">{{ failed_htlc.timestamp|naturaltime }}</td>
|
||||
<td>{{ failed_htlc.chan_id_in }}</td>
|
||||
<td>{{ failed_htlc.chan_id_out }}</td>
|
||||
<td><a href="/channel?={{ failed_htlc.chan_id_in }}" target="_blank">{{ failed_htlc.chan_id_in }}</a></td>
|
||||
<td><a href="/channel?={{ failed_htlc.chan_id_out }}" target="_blank">{{ failed_htlc.chan_id_out }}</a></td>
|
||||
<td>{% if failed_htlc.chan_in_alias == '' %}---{% else %}{{ failed_htlc.chan_in_alias }}{% endif %}</td>
|
||||
<td>{% if failed_htlc.chan_out_alias == '' %}---{% else %}{{ failed_htlc.chan_out_alias }}{% endif %}</td>
|
||||
<td>{{ failed_htlc.amount|intcomma }}</td>
|
||||
|
|
@ -40,4 +40,4 @@
|
|||
<center><h1>You dont have any failed HTLCs yet.</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -4,30 +4,28 @@
|
|||
{% load humanize %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h3><a href="{{ graph_links }}/{{ network }}node/{{ node_info.identity_pubkey }}" target="_blank">{{ node_info.alias }}</a> | {{ node_info.identity_pubkey }}</h3>
|
||||
<h4>Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | <a href="/peers" target="_blank">Peers</a>: {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %}</h4>
|
||||
<h4>Public Capacity: {{ total_capacity|intcomma }} | Active Channels: {{ node_info.num_active_channels }} / {{ total_channels }} | <a href="/peers" target="_blank">Peers</a>: {{ node_info.num_peers }} | DB Size: {% if db_size > 0 %}{{ db_size }} GB{% else %}---{% endif %} | Total States Updates: {% if num_updates > 0 %}{{ num_updates|intcomma }} {% else %}---{% endif %}</h4>
|
||||
{% if total_private > 0 %}<h4>Private Capacity: {{ private_capacity|intcomma }} | Locked Liquidity: {{ private_outbound|intcomma }} | Active Private Channels: {{ active_private }} / {{ total_private }}</h4>{% endif %}
|
||||
<h4>Public Address: {% for info in node_info.uris %}{{ info }} | {% endfor %}</h4>
|
||||
<h4>Lnd sync: {{ node_info.synced_to_graph }} | chain sync: {{ node_info.synced_to_chain }} | {% for info in node_info.chains %}{{ info }}{% endfor %} | {{ node_info.block_height }} | {{ node_info.block_hash }}</h4>
|
||||
</div>
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h4>Wallet Balance: {{ balances.total_balance|intcomma }} | Confirmed Wallet Balance: {{ balances.confirmed_balance|intcomma }} | Unconfirmed Wallet Balance: {{ balances.unconfirmed_balance|intcomma }} | <a href="/balances" target="_blank">Details</a></h4>
|
||||
<h4>Total Balance: {{ total_balance|intcomma }} | Onchain Balance: {{ balances.total_balance|intcomma }} | Confirmed Balance: {{ balances.confirmed_balance|intcomma }} | Unconfirmed Balance: {{ balances.unconfirmed_balance|intcomma }} | <a href="/balances" target="_blank">Details</a></h4>
|
||||
<form action="/newaddress/" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="Get New Deposit Address">
|
||||
<input type="submit" value="Get New Onchain Address">
|
||||
</form>
|
||||
</div>
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h4>Completed Payments: {{ total_payments }} | Sats Sent: {{ total_sent|intcomma }} | Fees Paid: {{ fees_paid|intcomma }} [{{ payments_ppm|intcomma }}]</h4>
|
||||
<h4>Paid Invoices: {{ total_invoices }} | Sats Received: {{ total_received|intcomma }}</h4>
|
||||
<h4>Lifetime Routed: {{ total_forwards|intcomma }} | Value: {{ total_value_forwards|intcomma }} | Fees Earned: {{ earned|intcomma }} [{{ routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs|intcomma }} | Percent Cost: {{ percent_cost }}%</h4>
|
||||
<h4>7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound|intcomma }} [{{ profit_per_outbound_real|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%</h4>
|
||||
<h4>1-Day Routed: {{ routed_1day|intcomma }} | Value: {{ routed_1day_amt|intcomma }} | Fees Earned: {{ earned_1day|intcomma }} [{{ 1day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_1day|intcomma }} | Offchain Fees: {{ total_1day_fees|intcomma }} [{{ 1day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_1day }}% | Profit/Outbound: {{ profit_per_outbound_1d|intcomma }} [{{ profit_per_outbound_real_1d|intcomma }}] | Outbound Utilization: {{ routed_1day_percent }}%</h4>
|
||||
<h4>7-Day Routed: {{ routed_7day|intcomma }} | Value: {{ routed_7day_amt|intcomma }} | Fees Earned: {{ earned_7day|intcomma }} [{{ 7day_routed_ppm|intcomma }}] | Onchain Fees: {{ onchain_costs_7day|intcomma }} | Offchain Fees: {{ total_7day_fees|intcomma }} [{{ 7day_payments_ppm|intcomma }}] | Percent Cost: {{ percent_cost_7day }}% | Profit/Outbound: {{ profit_per_outbound_7d|intcomma }} [{{ profit_per_outbound_real_7d|intcomma }}] | Outbound Utilization: {{ routed_7day_percent }}%</h4>
|
||||
</div>
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h4>Total Inbound Liquidity: {{ sum_inbound|intcomma }} | Outbound Liquidity: {{ sum_outbound|intcomma }} | Liquidity Ratio: {{ liq_ratio }}%</h4>
|
||||
<h4>Active Inbound Liquidity: {{ inbound|intcomma }} | Outbound Liquidity: {{ outbound|intcomma }} | Unsettled Liquidity: {{ unsettled|intcomma }}</h4>
|
||||
<h4>Inactive Inbound Liquidity: {{ inactive_inbound|intcomma }} | Outbound Liquidity: {{ inactive_outbound|intcomma }} | Unsettled Liquidity: {{ inactive_unsettled|intcomma }}</h4>
|
||||
<h4>Balance In Limbo: {{ limbo_balance|intcomma }} | Total Unsettled Liquidity: {{ total_unsettled|intcomma }} | <a href="/pending_htlcs" target="_blank">Pending HTLCs</a>: {{ pending_htlc_count }}</h4>
|
||||
<h4><a href="/closures" target="_blank">Closures</a> | <a href="/opens" target="_blank">New Peers</a> | <a href="/actions" target="_blank">AR Actions</a> | <a href="/fees" target="_blank">Fee Rates</a> | <a href="/autopilot" target="_blank">Autopilot</a> | <a href="/autofees" target="_blank">Autofees</a> | <a href="/channels" target="_blank">Channel Performance</a> | <a href="/keysends" target="_blank">Keysends</a> | <a href="/rebalancing" target="_blank">Rebalancing</a> | <a href="/towers" target="_blank">Towers</a> | <a href="/batch" target="_blank">Batching</a> | <a href="/advanced" target="_blank">Advanced Settings</a></h4>
|
||||
<h4><a href="/income" target="_blank">P&L</a> | <a href="/closures" target="_blank">Closures</a> | <a href="/opens" target="_blank">New Peers</a> | <a href="/actions" target="_blank">AR Actions</a> | <a href="/fees" target="_blank">Fee Rates</a> | <a href="/autopilot" target="_blank">Autopilot</a> | <a href="/autofees" target="_blank">Autofees</a> | <a href="/channels" target="_blank">Channel Performance</a> | <a href="/keysends" target="_blank">Keysends</a> | <a href="/rebalancing" target="_blank">Rebalancing</a> | <a href="/towers" target="_blank">Towers</a> | <a href="/batch" target="_blank">Batching</a> | <a href="/advanced" target="_blank">Advanced Settings</a></h4>
|
||||
</div>
|
||||
{% if active_channels %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
|
|
@ -44,10 +42,10 @@
|
|||
<th>Unsettled</th>
|
||||
<th>oRate</th>
|
||||
<th>oBase</th>
|
||||
<th>o1D</th>
|
||||
<th>i1D</th>
|
||||
<th>o7D</th>
|
||||
<th>i7D</th>
|
||||
<th>oLife</th>
|
||||
<th>iLife</th>
|
||||
<th>iRate</th>
|
||||
<th>iBase</th>
|
||||
<th title="When AR is ENABLED for the channel, keep pulling IN to the channel until its inbound liquidity falls below the iTarget%." width=4%>iTarget%</th>
|
||||
|
|
@ -64,10 +62,10 @@
|
|||
<td>{{ channel.unsettled_balance|intcomma }} ({{ channel.htlc_count }})</td>
|
||||
<td {% if channel.local_disabled == True %}style="background-color: #fadbd5"{% endif %}>{{ channel.local_fee_rate|intcomma }}</td>
|
||||
<td {% if channel.local_disabled == True %}style="background-color: #fadbd5"{% endif %}>{{ channel.local_base_fee|intcomma }}</td>
|
||||
<td>{{ channel.amt_routed_out_1day|intcomma }} M ({{ channel.routed_out_1day }})</td>
|
||||
<td>{{ channel.amt_routed_in_1day|intcomma }} M ({{ channel.routed_in_1day }})</td>
|
||||
<td>{{ channel.amt_routed_out_7day|intcomma }} M ({{ channel.routed_out_7day }})</td>
|
||||
<td>{{ channel.amt_routed_in_7day|intcomma }} M ({{ channel.routed_in_7day }})</td>
|
||||
<td>{{ channel.amt_routed_out|intcomma }} M ({{ channel.routed_out }})</td>
|
||||
<td>{{ channel.amt_routed_in|intcomma }} M ({{ channel.routed_in }})</td>
|
||||
<td {% if channel.remote_disabled == True %}style="background-color: #fadbd5"{% endif %}>{{ channel.remote_fee_rate|intcomma }}</td>
|
||||
<td {% if channel.remote_disabled == True %}style="background-color: #fadbd5"{% endif %}>{{ channel.remote_base_fee|intcomma }}</td>
|
||||
<td>
|
||||
|
|
@ -216,18 +214,106 @@
|
|||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Commit Fee</th>
|
||||
<th>AF</th>
|
||||
<th>Fee Rate</th>
|
||||
<th>Base Fee</th>
|
||||
<th>CLTV</th>
|
||||
<th>Amt</th>
|
||||
<th>Max Cost%</th>
|
||||
<th>oTarget%</th>
|
||||
<th>iTarget%</th>
|
||||
<th>AR</th>
|
||||
</tr>
|
||||
{% for channel in pending_open %}
|
||||
<tr>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
<td title="Commit Fee: {{ channel.commit_fee }}">{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.commit_fee }}</td>
|
||||
<td {% if channel.auto_fees == False %}style="background-color: #fadbd5"{% else %}style="background-color: #a6dce2"{% endif %}>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="{% if channel.auto_fees == True %}Disable{% else %}Enable{% endif %}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="8">
|
||||
<input type="hidden" name="target" value="0">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="0" max="100000" name="target" value="{{ channel.local_fee_rate }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="1">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="0" max="100000" name="target" value="{{ channel.local_base_fee }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="0">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="18" max="1000" name="target" value="{{ channel.local_cltv }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="9">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="1" max="100000000" name="target" value="{{ channel.ar_amt_target }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="2">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="1" max="100" name="target" value="{{ channel.ar_max_cost }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="6">
|
||||
</form>
|
||||
</td>
|
||||
<td {% if channel.auto_rebalance == False %}style="background-color: #80ced6"{% else %}style="background-color: #f18973"{% endif %}>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="1" max="100" name="target" value="{{ channel.ar_out_target }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="4">
|
||||
</form>
|
||||
</td>
|
||||
<td {% if channel.auto_rebalance == True %}style="background-color: #80ced6"{% else %}style="background-color: #f18973"{% endif %}>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input style="text-align:center" id="target" type="number" min="1" max="100" name="target" value="{{ channel.ar_in_target }}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="3">
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/update_pending/" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="submit" value="{% if channel.auto_rebalance == True %}Disable{% else %}Enable{% endif %}">
|
||||
<input type="hidden" name="funding_txid" value="{{ channel.funding_txid }}">
|
||||
<input type="hidden" name="output_index" value="{{ channel.output_index }}">
|
||||
<input type="hidden" name="update_target" value="5">
|
||||
<input type="hidden" name="target" value="0">
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -240,25 +326,23 @@
|
|||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Balance In Limbo</th>
|
||||
<th>Local Commit Fee</th>
|
||||
<th width=20%>Closing TX</th>
|
||||
</tr>
|
||||
{% for channel in pending_closed %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.channel_point }}"><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.limbo_balance|intcomma }}</td>
|
||||
<td>{{ channel.commitments.local_commit_fee_sat }}</td>
|
||||
<td>{{ channel.local_commit_fee_sat|intcomma }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -271,7 +355,6 @@
|
|||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
|
|
@ -281,11 +364,8 @@
|
|||
</tr>
|
||||
{% for channel in pending_force_closed %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.channel_point }}"><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
|
|
@ -304,25 +384,23 @@
|
|||
<tr>
|
||||
<th>Channel ID</th>
|
||||
<th>Peer Alias</th>
|
||||
<th width=20%>Channel Point</th>
|
||||
<th>Capacity</th>
|
||||
<th>Local Balance</th>
|
||||
<th>Remote Balance</th>
|
||||
<th>Balance In Limbo</th>
|
||||
<th>Local Commit Fee</th>
|
||||
<th width=20%>Closing TX</th>
|
||||
</tr>
|
||||
{% for channel in waiting_for_close %}
|
||||
<tr>
|
||||
<td><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.channel_point }}"><a href="/channel?={{ channel.chan_id }}" target="_blank">{{ channel.chan_id }}</a></td>
|
||||
<td title="{{ channel.remote_node_pub }}"><a href="{{ graph_links }}/{{ network }}node/{{ channel.remote_node_pub }}" target="_blank">{% if channel.alias == '' %}{{ channel.remote_node_pub|slice:":12" }}{% else %}{{ channel.alias }}{% endif %}</a></td>
|
||||
{% with funding_txid=channel.channel_point|slice:":-2" %}
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ funding_txid }}' target="_blank">{{ channel.channel_point }}</a></td>
|
||||
{% endwith %}
|
||||
<td>{{ channel.capacity|intcomma }}</td>
|
||||
<td>{{ channel.local_balance|intcomma }}</td>
|
||||
<td>{{ channel.remote_balance|intcomma }}</td>
|
||||
<td>{{ channel.limbo_balance|intcomma }}</td>
|
||||
<td>{{ channel.commitments.local_commit_fee_sat }}</td>
|
||||
<td>{{ channel.local_commit_fee_sat|intcomma }}</td>
|
||||
<td><a href='{{ network_links }}/{{ network }}tx/{{ channel.closing_txid }}' target="_blank">{{ channel.closing_txid }}</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
@ -352,7 +430,7 @@
|
|||
<td>{% if forward.chan_out_alias == '' %}---{% else %}{{ forward.chan_out_alias }}{% endif %}</td>
|
||||
<td><a href="/channel?={{ forward.chan_id_in }}" target="_blank">{{ forward.chan_id_in }}</a></td>
|
||||
<td><a href="/channel?={{ forward.chan_id_out }}" target="_blank">{{ forward.chan_id_out }}</a></td>
|
||||
<td>{{ forward.fee }}</td>
|
||||
<td>{{ forward.fee|intcomma }}</td>
|
||||
<td>{{ forward.ppm|intcomma }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -361,28 +439,32 @@
|
|||
{% endif %}
|
||||
{% if rebalances %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Last 10 <a href="/rebalancing" target="_blank">Rebalance Requests</a> (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)</h2>
|
||||
<h2>Last 10 <a href="/rebalances" target="_blank">Rebalance Requests</a> (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Requested</th>
|
||||
<th>Start</th>
|
||||
<th>Stop</th>
|
||||
<th>Scheduled Duration</th>
|
||||
<th>Actual Duration</th>
|
||||
<th>Value</th>
|
||||
<th>Fee Limit</th>
|
||||
<th>Target PPM</th>
|
||||
<th>Fees Paid</th>
|
||||
<th>Last Hop Alias</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{% for rebalance in rebalances %}
|
||||
<tr>
|
||||
<td title="{{ rebalance.requested }}">{{ rebalance.requested|naturaltime }}</td>
|
||||
<td title="{{ rebalance.start }}">{% if rebalance.status == 0 %}N/A{% else %}{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td title="{{ rebalance.stop }}">{% if rebalance.status > 1 %}{{ rebalance.stop|naturaltime }}{% else %}N/A{% endif %}</td>
|
||||
<td title="{{ rebalance.start }}">{% if rebalance.status == 0 %}---{% else %}{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td title="{{ rebalance.stop }}">{% if rebalance.status > 1 %}{{ rebalance.stop|naturaltime }}{% else %}---{% endif %}</td>
|
||||
<td>{{ rebalance.duration }} minutes</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}</td>
|
||||
<td>{{ rebalance.value|intcomma }}</td>
|
||||
<td>{{ rebalance.fee_limit|intcomma }}</td>
|
||||
<td>{{ rebalance.ppm|intcomma }}</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma}}{% else %}---{% endif %}</td>
|
||||
<td>{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}</td>
|
||||
<td title="{{ rebalance.status }}">{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}<a href="/route?={{ rebalance.payment_hash }}" target="_blank">Successful</a>{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}</td>
|
||||
</tr>
|
||||
|
|
@ -414,9 +496,9 @@
|
|||
<td>{{ payment.fee|intcomma }}</td>
|
||||
<td>{{ payment.ppm|intcomma }}</td>
|
||||
<td>{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out != 'MPP' %}<a href="/channel?={{ payment.chan_out }}" target="_blank">{{ payment.chan_out }}</a>{% else %}{{ payment.chan_out }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out != 'MPP' %}<a href="/channel?={{ payment.chan_out }}" target="_blank">{{ payment.chan_out }}</a>{% else %}{{ payment.chan_out }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ payment.message }}">{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -441,13 +523,13 @@
|
|||
{% for invoice in invoices %}
|
||||
<tr>
|
||||
<td title="{{ invoice.creation_date }}">{{ invoice.creation_date|naturaltime }}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}</td>
|
||||
<td>{{ invoice.r_hash }}</td>
|
||||
<td>{{ invoice.value|add:"0"|intcomma }}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ invoice.message }}">{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -517,6 +599,8 @@
|
|||
<input id="target_time" type="number" min="1" max="60" name="target_time">
|
||||
<label title="When a channel is not enabled for targeting; the minimum outbound a channel must have to be a source for refilling another channel" for="outbound_percent">Target Outbound Above (%): </label>
|
||||
<input id="outbound_percent" type="number" step="1" min="1" max="100" name="outbound_percent">
|
||||
<label title="When a channel is enabled for targeting; the maximum inbound a channel can have before selected for auto rebalance" for="inbound_percent">Target Inbound Above (%): </label>
|
||||
<input id="inbound_percent" type="number" step="1" min="1" max="100" name="inbound_percent">
|
||||
<label title="The max rate we can ever use to refill a channel with outbound" for="fee_rate">Global Max Fee Rate (ppm): </label>
|
||||
<input id="fee_rate" type="number" min="1" max="2500" name="fee_rate">
|
||||
<label title="The ppm to target which is the % of the outbound fee rate for the channel being refilled" for="max_cost">Max Cost (%): </label>
|
||||
|
|
@ -527,6 +611,10 @@
|
|||
<input id="wait_period" type="number" min="1" max="100" name="wait_period">
|
||||
<label title="This enables or disables the Autopilot function which automatically acts upon suggestions on this page: /actions" for="autopilot">Autopilot: </label>
|
||||
<input id="autopilot" type="number" min="0" max="1" name="autopilot">
|
||||
<label title="Number of days to consider for autopilot. Default 7." for="autopilotdays">AutopilotDays: </label>
|
||||
<input id="autopilotdays" type="number" min="0" max="100" name="autopilotdays">
|
||||
<label title="Apply for all existing channels." for="targetallchannels">Target All Channels:? </label>
|
||||
<input id="targetallchannels" type="checkbox" name="targetallchannels">
|
||||
<input type="submit" value="OK">
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
74
gui/templates/income.html
Normal file
74
gui/templates/income.html
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %} {{ block.super }} - P&L{% endblock %}
|
||||
{% block content %}
|
||||
{% load humanize %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>P&L Statement For <a href="{{ graph_links }}/{{ network }}node/{{ node_info.identity_pubkey }}" target="_blank">{% if node_info.alias != "" %}{{ node_info.alias }}</a> ({{ node_info.identity_pubkey }}){% else %}{{ node_info.identity_pubkey }}</a>{% endif %}</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Line Item</th>
|
||||
<th>1 Day</th>
|
||||
<th>7 Day</th>
|
||||
<th>30 Day</th>
|
||||
<th>90 Day</th>
|
||||
<th>Lifetime</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Payments Routed</td>
|
||||
<td>{{ forward_count_1day|intcomma }}</td>
|
||||
<td>{{ forward_count_7day|intcomma }}</td>
|
||||
<td>{{ forward_count_30day|intcomma }}</td>
|
||||
<td>{{ forward_count_90day|intcomma }}</td>
|
||||
<td>{{ forward_count|intcomma }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Value Routed</td>
|
||||
<td>{{ forward_amount_1day|intcomma }}</td>
|
||||
<td>{{ forward_amount_7day|intcomma }}</td>
|
||||
<td>{{ forward_amount_30day|intcomma }}</td>
|
||||
<td>{{ forward_amount_90day|intcomma }}</td>
|
||||
<td>{{ forward_amount|intcomma }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Revenue Earned</td>
|
||||
<td>{{ total_revenue_1day|intcomma }} [{{ total_revenue_ppm_1day|intcomma }}]</td>
|
||||
<td>{{ total_revenue_7day|intcomma }} [{{ total_revenue_ppm_7day|intcomma }}]</td>
|
||||
<td>{{ total_revenue_30day|intcomma }} [{{ total_revenue_ppm_30day|intcomma }}]</td>
|
||||
<td>{{ total_revenue_90day|intcomma }} [{{ total_revenue_ppm_90day|intcomma }}]</td>
|
||||
<td>{{ total_revenue|intcomma }} [{{ total_revenue_ppm|intcomma }}]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Onchain Costs</td>
|
||||
<td>{{ onchain_costs_1day|intcomma }}</td>
|
||||
<td>{{ onchain_costs_7day|intcomma }}</td>
|
||||
<td>{{ onchain_costs_30day|intcomma }}</td>
|
||||
<td>{{ onchain_costs_90day|intcomma }}</td>
|
||||
<td>{{ onchain_costs|intcomma }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Offchain Costs</td>
|
||||
<td>{{ total_fees_1day|intcomma }} [{{ total_fees_ppm_1day }}]</td>
|
||||
<td>{{ total_fees_7day|intcomma }} [{{ total_fees_ppm_7day }}]</td>
|
||||
<td>{{ total_fees_30day|intcomma }} [{{ total_fees_ppm_30day }}]</td>
|
||||
<td>{{ total_fees_90day|intcomma }} [{{ total_fees_ppm_90day }}]</td>
|
||||
<td>{{ total_fees|intcomma }} [{{ total_fees_ppm }}]</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Percent Cost</td>
|
||||
<td>{{ percent_cost_1day|intcomma }}%</td>
|
||||
<td>{{ percent_cost_7day|intcomma }}%</td>
|
||||
<td>{{ percent_cost_30day|intcomma }}%</td>
|
||||
<td>{{ percent_cost_90day|intcomma }}%</td>
|
||||
<td>{{ percent_cost|intcomma }}%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Profits</td>
|
||||
<td>{{ profits_1day|intcomma }} [{{ profits_ppm_1day }}]</td>
|
||||
<td>{{ profits_7day|intcomma }} [{{ profits_ppm_7day }}]</td>
|
||||
<td>{{ profits_30day|intcomma }} [{{ profits_ppm_30day }}]</td>
|
||||
<td>{{ profits_90day|intcomma }} [{{ profits_ppm_90day }}]</td>
|
||||
<td>{{ profits|intcomma }} [{{ profits_ppm }}]</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
@ -20,13 +20,13 @@
|
|||
{% for invoice in invoices %}
|
||||
<tr>
|
||||
<td title="{{ invoice.creation_date }}">{{ invoice.creation_date|naturaltime }}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}N/A{% endif %}</td>
|
||||
<td title="{{ invoice.settle_date }}">{% if invoice.state == 1 %}{{ invoice.settle_date|naturaltime }}{% else %}---{% endif %}</td>
|
||||
<td>{{ invoice.r_hash }}</td>
|
||||
<td>{{ invoice.value|add:"0"|intcomma }}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{{ invoice.amt_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 0 %}Open{% elif invoice.state == 1 %}Settled{% elif invoice.state == 2 %}Canceled{% else %}{{ invoice.state }}{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 %}{% if invoice.chan_in_alias == '' %}---{% else %}{{ invoice.chan_in_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if invoice.state == 1 and invoice.chan_in != None %}<a href="/channel?={{ invoice.chan_in }}" target="_blank">{{ invoice.chan_in }}</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ invoice.message }}">{% if invoice.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -38,4 +38,4 @@
|
|||
<center><h1>You dont have any invoices yet.</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<tr>
|
||||
<td><a href="{{ graph_links }}/{{ network }}node/{{ node.node_pubkey }}" target="_blank">{{ node.node_pubkey }}</a></td>
|
||||
<td>{% if node.alias == '' %}---{% else %}{{ node.alias }}{% endif %}</td>
|
||||
<td>{{ node.count }}</td>
|
||||
<td><a href="/routes?={{ node.node_pubkey }}" target="_blank">{{ node.count }}</a></td>
|
||||
<td>{{ node.amount|add:"0"|intcomma }}</td>
|
||||
<td>{{ node.fees|add:"0"|intcomma }}</td>
|
||||
<td>{{ node.ppm|add:"0"|intcomma }}</td>
|
||||
|
|
@ -38,4 +38,4 @@
|
|||
<center><h1>No potential peers can be calculated yet, try waiting until you have some payment data.</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@
|
|||
<td>{{ payment.fee|intcomma }}</td>
|
||||
<td>{{ payment.ppm|intcomma }}</td>
|
||||
<td>{% if payment.status == 1 %}In-Flight{% elif payment.status == 2 %}Succeeded{% elif payment.status == 3 %}Failed{% else %}{{ payment.status }}{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/channel?={{ payment.chan_out }}" target="_blank">{{ payment.chan_out }}</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}N/A{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}{% if payment.chan_out_alias == '' %}---{% else %}{{ payment.chan_out_alias }}{% endif %}{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/channel?={{ payment.chan_out }}" target="_blank">{{ payment.chan_out }}</a>{% else %}---{% endif %}</td>
|
||||
<td>{% if payment.status == 2 %}<a href="/route?={{ payment.payment_hash }}" target="_blank">Open</a>{% else %}---{% endif %}</td>
|
||||
<td title="{{ payment.message }}">{% if payment.keysend_preimage != None %}Yes{% else %}No{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -40,4 +40,4 @@
|
|||
<center><h1>You dont have any payments yet.</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
</tr>
|
||||
{% for htlc in outgoing_htlcs %}
|
||||
<tr>
|
||||
<td>{{ htlc.chan_id }}</td>
|
||||
<td><a href="/channel?={{ htlc.chan_id }}" target="_blank">{{ htlc.chan_id }}</a></td>
|
||||
<td>{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}</td>
|
||||
<td>{{ htlc.forwarding_channel }}</td>
|
||||
<td>{% if htlc.forwarding_channel == 0 %}---{% else %}<a href="/channel?={{ htlc.forwarding_channel }}" target="_blank">{{ htlc.forwarding_channel }}</a>{% endif %}</td>
|
||||
<td>{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}</td>
|
||||
<td>{{ htlc.amount|intcomma }}</td>
|
||||
<td title="{{ htlc.blocks_til_expiration|intcomma }} blocks to {{ htlc.expiration_height|intcomma }}">{{ htlc.hours_til_expiration }} hours</td>
|
||||
|
|
@ -44,9 +44,9 @@
|
|||
</tr>
|
||||
{% for htlc in incoming_htlcs %}
|
||||
<tr>
|
||||
<td>{{ htlc.chan_id }}</td>
|
||||
<td><a href="/channel?={{ htlc.chan_id }}" target="_blank">{{ htlc.chan_id }}</a></td>
|
||||
<td>{% if htlc.alias == '' %}---{% else %}{{ htlc.alias }}{% endif %}</td>
|
||||
<td>{{ htlc.forwarding_channel }}</td>
|
||||
<td>{% if htlc.forwarding_channel == 0 %}---{% else %}<a href="/channel?={{ htlc.forwarding_channel }}" target="_blank">{{ htlc.forwarding_channel }}</a>{% endif %}</td>
|
||||
<td>{% if htlc.forwarding_alias == '' %}---{% else %}{{ htlc.forwarding_alias }}{% endif %}</td>
|
||||
<td>{{ htlc.amount|intcomma }}</td>
|
||||
<td title="{{ htlc.blocks_til_expiration|intcomma }} blocks to {{ htlc.expiration_height|intcomma }}">{{ htlc.hours_til_expiration }} hours</td>
|
||||
|
|
@ -61,4 +61,4 @@
|
|||
<center><h1>No pending HTLCs were found!</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
45
gui/templates/rebalances.html
Normal file
45
gui/templates/rebalances.html
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{% extends "base.html" %}
|
||||
{% block title %} {{ block.super }} - Rebalances{% endblock %}
|
||||
{% block content %}
|
||||
{% load humanize %}
|
||||
{% if rebalances %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Last 150 Rebalances</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Requested</th>
|
||||
<th>Start</th>
|
||||
<th>Stop</th>
|
||||
<th>Scheduled Duration</th>
|
||||
<th>Actual Duration</th>
|
||||
<th>Value</th>
|
||||
<th>Fee Limit</th>
|
||||
<th>Target PPM</th>
|
||||
<th>Fees Paid</th>
|
||||
<th>Last Hop Alias</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{% for rebalance in rebalances %}
|
||||
<tr>
|
||||
<td title="{{ rebalance.requested }}">{{ rebalance.requested|naturaltime }}</td>
|
||||
<td {% if rebalance.status == 0 %}>---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td {% if rebalance.status > 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}</td>
|
||||
<td>{{ rebalance.duration }} minutes</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}</td>
|
||||
<td>{{ rebalance.value|intcomma }}</td>
|
||||
<td>{{ rebalance.fee_limit|intcomma }}</td>
|
||||
<td>{{ rebalance.ppm|intcomma }}</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}</td>
|
||||
<td title="{{ rebalance.status }}">{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}<a href="/route?={{ rebalance.payment_hash }}" target="_blank">Successful</a>{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not rebalances %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<center><h1>You dont have any rebalances yet.</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
{% load humanize %}
|
||||
{% if channels %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing)</h2>
|
||||
<h2>Channel Rebalancing (currently scheduling {{ eligible_count }} of {{ enabled_count }} enabled channels for rebalancing via {{ available_count }} outbound channels)</h2>
|
||||
<div class="w3-container w3-padding-small" style="overflow-x: scroll">
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
<th title="When AR is ENABLED for the channel, the size of the rebalance attempts that should be tried during attempts to refill the channel.">Target Amt</th>
|
||||
<th title="When AR is ENABLED, the maximum percentage amount of the local fee rate that can be used for the max rebalancing cost.">Max Cost %</th>
|
||||
<th title="When AR is NOT ENABLED for the channel, keep pushing OUT the channel until its outbound liquidity falls below the oTarget%.">oTarget%</th>
|
||||
<th>iTarget%</th>
|
||||
<th title="When AR is ENABLED for the channel, keep pulling IN the channel until its inbound liquidity is above the iTarget%.">iTarget%</th>
|
||||
<th>AR</th>
|
||||
<th title="The rate of successful rebalances on this channel.">7-Day Rate</th>
|
||||
<th>Active</th>
|
||||
|
|
@ -94,28 +94,32 @@
|
|||
{% endif %}
|
||||
{% if rebalancer %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Last 20 Rebalance Requests</h2>
|
||||
<h2>Last 20 <a href="/rebalances" target="_blank">Rebalance Requests</a></h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Requested</th>
|
||||
<th>Start</th>
|
||||
<th>Stop</th>
|
||||
<th>Scheduled Duration</th>
|
||||
<th>Actual Duration</th>
|
||||
<th>Value</th>
|
||||
<th>Fee Limit</th>
|
||||
<th>Target PPM</th>
|
||||
<th>Fees Paid</th>
|
||||
<th>Last Hop Alias</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
{% for rebalance in rebalancer %}
|
||||
<tr>
|
||||
<td title="{{ rebalance.requested }}">{{ rebalance.requested|naturaltime }}</td>
|
||||
<td {% if rebalance.status == 0 %}>N/A{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td {% if rebalance.status > 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>N/A{% endif %}</td>
|
||||
<td {% if rebalance.status == 0 %}>---{% else %}title="{{ rebalance.start }}">{{ rebalance.start|naturaltime }}{% endif %}</td>
|
||||
<td {% if rebalance.status > 1 %}title="{{ rebalance.stop }}">{{ rebalance.stop|naturaltime }}{% else %}>---{% endif %}</td>
|
||||
<td>{{ rebalance.duration }} minutes</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.stop|timeuntil:rebalance.start }}{% else %}---{% endif %}</td>
|
||||
<td>{{ rebalance.value|intcomma }}</td>
|
||||
<td>{{ rebalance.fee_limit|intcomma }}</td>
|
||||
<td>{{ rebalance.ppm|intcomma }}</td>
|
||||
<td>{% if rebalance.status == 2 %}{{ rebalance.fees_paid|intcomma }}{% else %}---{% endif %}</td>
|
||||
<td>{% if rebalance.target_alias == '' %}None Specified{% else %}{{ rebalance.target_alias }}{% endif %}</td>
|
||||
<td title="{{ rebalance.status }}">{% if rebalance.status == 0 %}Pending{% elif rebalance.status == 1 %}In-Flight{% elif rebalance.status == 2 %}<a href="/route?={{ rebalance.payment_hash }}" target="_blank">Successful</a>{% elif rebalance.status == 3 %}Timeout{% elif rebalance.status == 4 %}No Route{% elif rebalance.status == 5 %}Error{% elif rebalance.status == 6 %}Incorrect Payment Details{% elif rebalance.status == 7 %}Insufficient Balance{% elif rebalance.status == 400 %}Rebalancer Request Failed{% elif rebalance.status == 408 %}Rebalancer Request Timeout{% else %}{{ rebalance.status }}{% endif %}</td>
|
||||
</tr>
|
||||
|
|
@ -158,6 +162,8 @@
|
|||
<input id="target_time" type="number" min="1" max="60" name="target_time">
|
||||
<label title="When a channel is not enabled for targeting; the minimum outbound a channel must have to be a source for refilling another channel" for="outbound_percent">Target Outbound Above (%): </label>
|
||||
<input id="outbound_percent" type="number" step="1" min="1" max="100" name="outbound_percent">
|
||||
<label title="When a channel is enabled for targeting; the maximum inbound a channel can have before selected for auto rebalance" for="inbound_percent">Target Inbound Above (%): </label>
|
||||
<input id="inbound_percent" type="number" step="1" min="1" max="100" name="inbound_percent">
|
||||
<label title="The max rate we can ever use to refill a channel with outbound" for="fee_rate">Global Max Fee Rate (ppm): </label>
|
||||
<input id="fee_rate" type="number" min="1" max="2500" name="fee_rate">
|
||||
<label title="The ppm to target which is the % of the outbound fee rate for the channel being refilled" for="max_cost">Max Cost (%): </label>
|
||||
|
|
@ -168,6 +174,10 @@
|
|||
<input id="wait_period" type="number" min="1" max="100" name="wait_period">
|
||||
<label title="This enables or disables the Autopilot function which automatically acts upon suggestions on this page: /actions" for="autopilot">Autopilot: </label>
|
||||
<input id="autopilot" type="number" min="0" max="1" name="autopilot">
|
||||
<label title="Number of days to consider for autopilot. Default 7. " for="autopilotdays">AutopilotDays: </label>
|
||||
<input id="autopilotdays" type="number" min="0" max="100" name="autopilotdays">
|
||||
<label title="Target All Channels for applicable channel level settings. Uncheck if you only want to update local settings for future channels." for="targetallchannels">AllChannels?: </label>
|
||||
<input id="targetallchannels" type="checkbox" name="targetallchannels">
|
||||
<input type="submit" value="OK">
|
||||
</form>
|
||||
</div>
|
||||
|
|
@ -197,4 +207,4 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<td>{% if resolution.outcome == 0 %}Unknown{% elif resolution.outcome == 1 %}Claimed{% elif resolution.outcome == 2 %}Unclaimed{% elif resolution.outcome == 3 %}Abandoned{% elif resolution.outcome == 4 %}First Stage{% elif resolution.outcome == 5 %}Timeout{% else %}{{ resolution.outcome }}{% endif %}</td>
|
||||
<td><a href="{{ network_links }}/{{ network }}tx/{{ resolution.outpoint_tx }}" target="_blank">{{ resolution.outpoint_tx }}:{{ resolution.outpoint_index }}</a></td>
|
||||
<td>{{ resolution.amount_sat|intcomma }}</td>
|
||||
<td><a href="{{ network_links }}/{{ network }}tx/{{ resolution.sweep_txid }}" target="_blank">{{ resolution.sweep_txid }}</a></td>
|
||||
<td>{% if resolution.resolution_type != 2 %}<a href="{{ network_links }}/{{ network }}tx/{{ resolution.sweep_txid }}" target="_blank">{{ resolution.sweep_txid }}</a>{% else %}---{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
{% load humanize %}
|
||||
{% if route %}
|
||||
<div class="w3-container w3-padding-small">
|
||||
<h2>Route For Payment: {{ payment_hash }}</h2>
|
||||
<h2>Route For : {{ payment_hash }}</h2>
|
||||
<table class="w3-table-all w3-centered w3-hoverable">
|
||||
<tr>
|
||||
<th>Step</th>
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<th>Fee</th>
|
||||
<th title="The fee in PPM paid on this hop.">PPM</th>
|
||||
<th title="The cost to get the payment to this hop of the payment.">Cost To</th>
|
||||
<th>📍</th>
|
||||
<th>Alias</th>
|
||||
<th>Channel ID</th>
|
||||
<th>Channel Capacity</th>
|
||||
|
|
@ -23,8 +24,9 @@
|
|||
<td>{{ hop.fee|intcomma }}</td>
|
||||
<td>{{ hop.ppm|intcomma }}</td>
|
||||
<td>{{ hop.cost_to|intcomma }}</td>
|
||||
<td>{% if hop.step == 1 %}📍{% else %}🔻{% endif %}
|
||||
<td>{% if hop.alias == '' %}---{% else %}{{ hop.alias }}{% endif %}</td>
|
||||
<td>{{ hop.chan_id }}</td>
|
||||
<td><a href="/channel?={{ hop.chan_id }}" target="_blank">{{ hop.chan_id }}</a></td>
|
||||
<td>{{ hop.chan_capacity|intcomma }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
@ -36,4 +38,4 @@
|
|||
<center><h1>A route was not found for this payment hash!</h1></center>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ router.register(r'failedhtlcs', views.FailedHTLCViewSet)
|
|||
urlpatterns = [
|
||||
path('', views.home, name='home'),
|
||||
path('route', views.route, name='route'),
|
||||
path('routes', views.routes, name='routes'),
|
||||
path('peers', views.peers, name='peers'),
|
||||
path('balances', views.balances, name='balances'),
|
||||
path('closures', views.closures, name='closures'),
|
||||
|
|
@ -34,6 +35,8 @@ urlpatterns = [
|
|||
path('payments', views.payments, name='payments'),
|
||||
path('invoices', views.invoices, name='invoices'),
|
||||
path('forwards', views.forwards, name='forwards'),
|
||||
path('income', views.income, name='income'),
|
||||
path('rebalances', views.rebalances, name='rebalances'),
|
||||
path('rebalancing', views.rebalancing, name='rebalancing'),
|
||||
path('openchannel/', views.open_channel_form, name='open-channel-form'),
|
||||
path('closechannel/', views.close_channel_form, name='close-channel-form'),
|
||||
|
|
@ -47,6 +50,7 @@ urlpatterns = [
|
|||
path('updatechanpolicy/', views.update_chan_policy, name='updatechanpolicy'),
|
||||
path('autorebalance/', views.auto_rebalance, name='auto-rebalance'),
|
||||
path('update_channel/', views.update_channel, name='update-channel'),
|
||||
path('update_pending/', views.update_pending, name='update-pending'),
|
||||
path('update_setting/', views.update_setting, name='update-setting'),
|
||||
path('opens/', views.opens, name='opens'),
|
||||
path('actions/', views.actions, name='actions'),
|
||||
|
|
@ -68,4 +72,4 @@ urlpatterns = [
|
|||
path('api/balances/', views.api_balances, name='api-balances'),
|
||||
path('api/pendingchannels/', views.pending_channels, name='pending-channels'),
|
||||
path('lndg-admin/', admin.site.urls),
|
||||
]
|
||||
]
|
||||
|
|
|
|||
528
gui/views.py
528
gui/views.py
|
|
@ -1,6 +1,6 @@
|
|||
from django.contrib import messages
|
||||
from django.shortcuts import get_object_or_404, render, redirect
|
||||
from django.db.models import Sum, IntegerField, FloatField, Count, F, Q
|
||||
from django.db.models import Sum, IntegerField, Count, F, Q
|
||||
from django.db.models.functions import Round
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.conf import settings
|
||||
|
|
@ -8,8 +8,8 @@ from datetime import datetime, timedelta
|
|||
from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import api_view
|
||||
from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm
|
||||
from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees
|
||||
from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, ChanPolicyForm, UpdateChannel, UpdateSetting, AutoRebalanceForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending
|
||||
from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels
|
||||
from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer
|
||||
from gui.lnd_deps import lightning_pb2 as ln
|
||||
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
|
||||
|
|
@ -53,38 +53,75 @@ def home(request):
|
|||
pending_closed = None
|
||||
pending_force_closed = None
|
||||
waiting_for_close = None
|
||||
pending_open_balance = 0
|
||||
if pending_channels.pending_open_channels:
|
||||
target_resp = pending_channels.pending_open_channels
|
||||
peers = Peers.objects.all()
|
||||
pending_open = [{'alias':peers.filter(pubkey=target_resp[i].channel.remote_node_pub)[0].alias if peers.filter(pubkey=target_resp[i].channel.remote_node_pub).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'commit_fee':target_resp[i].commit_fee,'commit_weight':target_resp[i].commit_weight,'fee_per_kw':target_resp[i].fee_per_kw} for i in range(0,len(target_resp))]
|
||||
pending_changes = PendingChannels.objects.all()
|
||||
pending_open = []
|
||||
inbound_setting = int(LocalSettings.objects.filter(key='AR-Inbound%')[0].value) if LocalSettings.objects.filter(key='AR-Inbound%').exists() else 100
|
||||
outbound_setting = int(LocalSettings.objects.filter(key='AR-Outbound%')[0].value) if LocalSettings.objects.filter(key='AR-Outbound%').exists() else 75
|
||||
amt_setting = float(LocalSettings.objects.filter(key='AR-Target%')[0].value) if LocalSettings.objects.filter(key='AR-Target%').exists() else 5
|
||||
cost_setting = int(LocalSettings.objects.filter(key='AR-MaxCost%')[0].value) if LocalSettings.objects.filter(key='AR-MaxCost%').exists() else 65
|
||||
auto_fees = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value) if LocalSettings.objects.filter(key='AF-Enabled').exists() else 0
|
||||
for i in range(0,len(target_resp)):
|
||||
item = {}
|
||||
pending_open_balance += target_resp[i].channel.local_balance
|
||||
funding_txid = target_resp[i].channel.channel_point.split(':')[0]
|
||||
output_index = target_resp[i].channel.channel_point.split(':')[1]
|
||||
updated = pending_changes.filter(funding_txid=funding_txid,output_index=output_index).exists()
|
||||
item['alias'] = peers.filter(pubkey=target_resp[i].channel.remote_node_pub)[0].alias if peers.filter(pubkey=target_resp[i].channel.remote_node_pub).exists() else None
|
||||
item['remote_node_pub'] = target_resp[i].channel.remote_node_pub
|
||||
item['channel_point'] = target_resp[i].channel.channel_point
|
||||
item['funding_txid'] = funding_txid
|
||||
item['output_index'] = output_index
|
||||
item['capacity'] = target_resp[i].channel.capacity
|
||||
item['local_balance'] = target_resp[i].channel.local_balance
|
||||
item['remote_balance'] = target_resp[i].channel.remote_balance
|
||||
item['local_chan_reserve_sat'] = target_resp[i].channel.local_chan_reserve_sat
|
||||
item['remote_chan_reserve_sat'] = target_resp[i].channel.remote_chan_reserve_sat
|
||||
item['initiator'] = target_resp[i].channel.initiator
|
||||
item['commitment_type'] = target_resp[i].channel.commitment_type
|
||||
item['commit_fee'] = target_resp[i].commit_fee
|
||||
item['commit_weight'] = target_resp[i].commit_weight
|
||||
item['fee_per_kw'] = target_resp[i].fee_per_kw
|
||||
item['local_base_fee'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_base_fee if updated else ''
|
||||
item['local_fee_rate'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_fee_rate if updated else ''
|
||||
item['local_cltv'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].local_cltv if updated else ''
|
||||
item['auto_rebalance'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_rebalance if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_rebalance != None else False
|
||||
item['ar_amt_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_amt_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_amt_target != None else int((amt_setting/100) * target_resp[i].channel.capacity)
|
||||
item['ar_in_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_in_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_in_target != None else inbound_setting
|
||||
item['ar_out_target'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_out_target if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_out_target != None else outbound_setting
|
||||
item['ar_max_cost'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_max_cost if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].ar_max_cost != None else cost_setting
|
||||
item['auto_fees'] = pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_fees if updated and pending_changes.filter(funding_txid=funding_txid,output_index=output_index)[0].auto_fees != None else (False if auto_fees == 0 else True)
|
||||
pending_open.append(item)
|
||||
if pending_channels.pending_closing_channels:
|
||||
target_resp = pending_channels.pending_closing_channels
|
||||
pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'limbo_balance':target_resp[i].limbo_balance} for i in range(0,len(target_resp))]
|
||||
pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,
|
||||
'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat,'limbo_balance':target_resp[i].limbo_balance,
|
||||
'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
|
||||
if pending_channels.pending_force_closing_channels:
|
||||
target_resp = pending_channels.pending_force_closing_channels
|
||||
pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity,'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity)))} for i in range(0,len(target_resp))]
|
||||
pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0],output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,
|
||||
'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else find_next_block_maturity(target_resp[i]),
|
||||
'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else 10*find_next_block_maturity(target_resp[i]) )))} for i in range(0,len(target_resp))]
|
||||
if pending_channels.waiting_close_channels:
|
||||
target_resp = pending_channels.waiting_close_channels
|
||||
waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'limbo_balance':target_resp[i].limbo_balance} for i in range(0,len(target_resp))]
|
||||
waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,
|
||||
'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,
|
||||
'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance,
|
||||
'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
|
||||
#Get recorded payment events
|
||||
payments = Payments.objects.exclude(status=3)
|
||||
total_payments = payments.filter(status=2).count()
|
||||
total_sent = 0 if total_payments == 0 else payments.filter(status=2).aggregate(Sum('value'))['value__sum']
|
||||
total_fees = 0 if total_payments == 0 else payments.aggregate(Sum('fee'))['fee__sum']
|
||||
#Get recorded invoice details
|
||||
invoices = Invoices.objects.exclude(state=2)
|
||||
total_invoices = invoices.filter(state=1).count()
|
||||
total_received = 0 if total_invoices == 0 else invoices.aggregate(Sum('amt_paid'))['amt_paid__sum']
|
||||
#Get recorded forwarding events
|
||||
forwards = Forwards.objects.all().annotate(amt_in=Sum('amt_in_msat')/1000).annotate(amt_out=Sum('amt_out_msat')/1000).annotate(ppm=Round((Sum('fee')*1000000000)/Sum('amt_out_msat'), output_field=IntegerField())).order_by('-id')
|
||||
forwards_df = DataFrame.from_records(forwards.values())
|
||||
total_forwards = forwards_df.shape[0]
|
||||
total_value_forwards = 0 if total_forwards == 0 else int(forwards_df['amt_out_msat'].sum()/1000)
|
||||
total_earned = 0 if total_forwards == 0 else forwards_df['fee'].sum()
|
||||
forwards_df_in_sum = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_in', as_index=True).sum()
|
||||
forwards_df_out_sum = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_out', as_index=True).sum()
|
||||
forwards_df_in_count = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_in', as_index=True).count()
|
||||
forwards_df_out_count = DataFrame() if forwards_df.empty else forwards_df.groupby('chan_id_out', as_index=True).count()
|
||||
#Get current active channels
|
||||
active_channels = channels.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by('outbound_percent')
|
||||
active_capacity = 0 if active_channels.count() == 0 else active_channels.aggregate(Sum('capacity'))['capacity__sum']
|
||||
|
|
@ -92,20 +129,33 @@ def home(request):
|
|||
active_outbound = 0 if active_capacity == 0 else active_channels.aggregate(Sum('local_balance'))['local_balance__sum']
|
||||
active_unsettled = 0 if active_capacity == 0 else active_channels.aggregate(Sum('unsettled_balance'))['unsettled_balance__sum']
|
||||
filter_7day = datetime.now() - timedelta(days=7)
|
||||
filter_1day = datetime.now() - timedelta(days=1)
|
||||
forwards_df_7d = DataFrame.from_records(forwards.filter(forward_date__gte=filter_7day).values())
|
||||
forwards_df_1d = DataFrame() if forwards_df_7d.empty else forwards_df_7d[forwards_df_7d['forward_date']>=filter_1day]
|
||||
forwards_df_in_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).sum()
|
||||
forwards_df_out_7d_sum = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).sum()
|
||||
forwards_df_in_7d_count = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_in', as_index=True).count()
|
||||
forwards_df_out_7d_count = DataFrame() if forwards_df_7d.empty else forwards_df_7d.groupby('chan_id_out', as_index=True).count()
|
||||
forwards_df_in_1d_sum = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_in', as_index=True).sum()
|
||||
forwards_df_out_1d_sum = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_out', as_index=True).sum()
|
||||
forwards_df_in_1d_count = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_in', as_index=True).count()
|
||||
forwards_df_out_1d_count = DataFrame() if forwards_df_1d.empty else forwards_df_1d.groupby('chan_id_out', as_index=True).count()
|
||||
routed_1day = forwards_df_1d.shape[0]
|
||||
routed_7day = forwards_df_7d.shape[0]
|
||||
routed_7day_amt = 0 if routed_7day == 0 else int(forwards_df_7d['amt_out_msat'].sum()/1000)
|
||||
routed_1day_amt = 0 if routed_1day == 0 else int(forwards_df_1d['amt_out_msat'].sum()/1000)
|
||||
total_earned_7day = 0 if routed_7day == 0 else forwards_df_7d['fee'].sum()
|
||||
total_earned_1day = 0 if routed_1day == 0 else forwards_df_1d['fee'].sum()
|
||||
payments_7day = payments.filter(status=2).filter(creation_date__gte=filter_7day)
|
||||
payments_7day_amt = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('value'))['value__sum']
|
||||
payments_1day = payments.filter(status=2).filter(creation_date__gte=filter_1day)
|
||||
payments_1day_amt = 0 if payments_1day.count() == 0 else payments_1day.aggregate(Sum('value'))['value__sum']
|
||||
total_7day_fees = 0 if payments_7day.count() == 0 else payments_7day.aggregate(Sum('fee'))['fee__sum']
|
||||
total_1day_fees = 0 if payments_1day.count() == 0 else payments_1day.aggregate(Sum('fee'))['fee__sum']
|
||||
pending_htlc_count = channels.filter(is_open=True).aggregate(Sum('htlc_count'))['htlc_count__sum'] if channels.filter(is_open=True).exists() else 0
|
||||
pending_outbound = channels.filter(is_open=True).aggregate(Sum('pending_outbound'))['pending_outbound__sum'] if channels.filter(is_open=True).exists() else 0
|
||||
pending_inbound = channels.filter(is_open=True).aggregate(Sum('pending_inbound'))['pending_inbound__sum'] if channels.filter(is_open=True).exists() else 0
|
||||
num_updates = channels.filter(is_open=True).aggregate(Sum('num_updates'))['num_updates__sum'] if channels.filter(is_open=True).exists() else 0
|
||||
detailed_active_channels = []
|
||||
for channel in active_channels:
|
||||
detailed_channel = {}
|
||||
|
|
@ -128,14 +178,14 @@ def home(request):
|
|||
detailed_channel['output_index'] = channel.output_index
|
||||
detailed_channel['outbound_percent'] = int(round(channel.outbound_percent/10, 0))
|
||||
detailed_channel['inbound_percent'] = int(round(channel.inbound_percent/10, 0))
|
||||
detailed_channel['routed_in'] = forwards_df_in_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['routed_out'] = forwards_df_out_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_in'] = int(forwards_df_in_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_out'] = int(forwards_df_out_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['routed_in_7day'] = forwards_df_in_7d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_7d_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['routed_out_7day'] = forwards_df_out_7d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_7d_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_in_7day'] = int(forwards_df_in_7d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_7d_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_out_7day'] = int(forwards_df_out_7d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_7d_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['routed_in_1day'] = forwards_df_in_1d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_in_1d_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['routed_out_1day'] = forwards_df_out_1d_count.loc[channel.chan_id].amt_out_msat if (forwards_df_out_1d_count.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_in_1day'] = int(forwards_df_in_1d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_in_1d_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['amt_routed_out_1day'] = int(forwards_df_out_1d_sum.loc[channel.chan_id].amt_out_msat//10000000)/100 if (forwards_df_out_1d_sum.index == channel.chan_id).any() else 0
|
||||
detailed_channel['htlc_count'] = channel.htlc_count
|
||||
detailed_channel['auto_rebalance'] = channel.auto_rebalance
|
||||
detailed_channel['ar_in_target'] = channel.ar_in_target
|
||||
|
|
@ -153,10 +203,16 @@ def home(request):
|
|||
sum_outbound = active_outbound + pending_outbound + inactive_outbound
|
||||
sum_inbound = active_inbound + pending_inbound + inactive_inbound
|
||||
onchain_txs = Onchain.objects.all()
|
||||
onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
|
||||
onchain_costs_7day = 0 if onchain_txs.filter(time_stamp__gte=filter_7day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_7day).aggregate(Sum('fee'))['fee__sum']
|
||||
total_costs = total_fees + onchain_costs
|
||||
onchain_costs_1day = 0 if onchain_txs.filter(time_stamp__gte=filter_1day).count() == 0 else onchain_txs.filter(time_stamp__gte=filter_1day).aggregate(Sum('fee'))['fee__sum']
|
||||
closures_7day = Closures.objects.filter(close_height__gte=(node_info.block_height - 1008))
|
||||
closures_1day = Closures.objects.filter(close_height__gte=(node_info.block_height - 144))
|
||||
close_fees_7day = channels.filter(chan_id__in=closures_7day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_7day.exists() else 0
|
||||
close_fees_1day = channels.filter(chan_id__in=closures_1day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_1day.exists() else 0
|
||||
onchain_costs_7day += close_fees_7day
|
||||
onchain_costs_1day += close_fees_1day
|
||||
total_costs_7day = total_7day_fees + onchain_costs_7day
|
||||
total_costs_1day = total_1day_fees + onchain_costs_1day
|
||||
#Get list of recent rebalance requests
|
||||
rebalances = Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')
|
||||
total_channels = node_info.num_active_channels + node_info.num_inactive_channels - private_count
|
||||
|
|
@ -170,27 +226,27 @@ def home(request):
|
|||
'node_info': node_info,
|
||||
'total_channels': total_channels,
|
||||
'balances': balances,
|
||||
'total_balance': balances.total_balance + sum_outbound + pending_open_balance + limbo_balance + private_outbound,
|
||||
'payments': payments.annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-creation_date')[:6],
|
||||
'total_sent': int(total_sent),
|
||||
'fees_paid': int(total_fees),
|
||||
'total_payments': total_payments,
|
||||
'invoices': invoices.order_by('-creation_date')[:6],
|
||||
'total_received': total_received,
|
||||
'total_invoices': total_invoices,
|
||||
'forwards': forwards_df.head(15).to_dict(orient='records'),
|
||||
'earned': int(total_earned),
|
||||
'total_forwards': total_forwards,
|
||||
'total_value_forwards': total_value_forwards,
|
||||
'forwards': forwards[:15],
|
||||
'routed_1day': routed_1day,
|
||||
'routed_7day': routed_7day,
|
||||
'routed_1day_amt': routed_1day_amt,
|
||||
'routed_7day_amt': routed_7day_amt,
|
||||
'earned_1day': int(total_earned_1day),
|
||||
'earned_7day': int(total_earned_7day),
|
||||
'routed_1day_percent': 0 if sum_outbound == 0 else int((routed_1day_amt/sum_outbound)*100),
|
||||
'routed_7day_percent': 0 if sum_outbound == 0 else int((routed_7day_amt/sum_outbound)*100),
|
||||
'profit_per_outbound': 0 if sum_outbound == 0 else int((total_earned_7day - total_7day_fees)/(sum_outbound/1000000)),
|
||||
'profit_per_outbound_real': 0 if sum_outbound == 0 else int((total_earned_7day - total_costs_7day)/(sum_outbound/1000000)),
|
||||
'percent_cost': 0 if total_earned == 0 else int((total_costs/total_earned)*100),
|
||||
'profit_per_outbound_1d': 0 if sum_outbound == 0 else int((total_earned_1day - total_1day_fees)/(sum_outbound/1000000)),
|
||||
'profit_per_outbound_real_1d': 0 if sum_outbound == 0 else int((total_earned_1day - total_costs_1day)/(sum_outbound/1000000)),
|
||||
'profit_per_outbound_7d': 0 if sum_outbound == 0 else int((total_earned_7day - total_7day_fees)/(sum_outbound/1000000)),
|
||||
'profit_per_outbound_real_7d': 0 if sum_outbound == 0 else int((total_earned_7day - total_costs_7day)/(sum_outbound/1000000)),
|
||||
'percent_cost_1day': 0 if total_earned_1day == 0 else int((total_costs_1day/total_earned_1day)*100),
|
||||
'percent_cost_7day': 0 if total_earned_7day == 0 else int((total_costs_7day/total_earned_7day)*100),
|
||||
'onchain_costs': onchain_costs,
|
||||
'onchain_costs_1day': onchain_costs_1day,
|
||||
'onchain_costs_7day': onchain_costs_7day,
|
||||
'total_1day_fees': int(total_1day_fees),
|
||||
'total_7day_fees': int(total_7day_fees),
|
||||
'active_channels': detailed_active_channels,
|
||||
'total_capacity': active_capacity + inactive_capacity,
|
||||
|
|
@ -219,17 +275,19 @@ def home(request):
|
|||
'local_settings': local_settings,
|
||||
'pending_htlc_count': pending_htlc_count,
|
||||
'failed_htlcs': FailedHTLCs.objects.all().order_by('-id')[:10],
|
||||
'payments_ppm': 0 if total_sent == 0 else int((total_fees/total_sent)*1000000),
|
||||
'routed_ppm': 0 if total_value_forwards == 0 else int((total_earned/total_value_forwards)*1000000),
|
||||
'1day_routed_ppm': 0 if routed_1day_amt == 0 else int((total_earned_1day/routed_1day_amt)*1000000),
|
||||
'7day_routed_ppm': 0 if routed_7day_amt == 0 else int((total_earned_7day/routed_7day_amt)*1000000),
|
||||
'1day_payments_ppm': 0 if payments_1day_amt == 0 else int((total_1day_fees/payments_1day_amt)*1000000),
|
||||
'7day_payments_ppm': 0 if payments_7day_amt == 0 else int((total_7day_fees/payments_7day_amt)*1000000),
|
||||
'liq_ratio': 0 if sum_outbound == 0 else int((sum_inbound/sum_outbound)*100),
|
||||
'eligible_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=True).annotate(inbound_can=((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity')).annotate(fee_ratio=(Sum('remote_fee_rate')*100)/Sum('local_fee_rate')).filter(inbound_can__gte=F('ar_in_target'), fee_ratio__lte=F('ar_max_cost')).count(),
|
||||
'enabled_count': channels.filter(is_open=True, auto_rebalance=True).count(),
|
||||
'available_count': channels.filter(is_active=True, is_open=True, private=False, auto_rebalance=False).annotate(outbound_can=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).filter(outbound_can__gte=F('ar_out_target')).count(),
|
||||
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
|
||||
'graph_links': graph_links(),
|
||||
'network_links': network_links(),
|
||||
'db_size': db_size
|
||||
'db_size': db_size,
|
||||
'num_updates': num_updates
|
||||
}
|
||||
return render(request, 'home.html', context)
|
||||
except Exception as e:
|
||||
|
|
@ -325,7 +383,7 @@ def channels(request):
|
|||
apy_7day = 0
|
||||
apy_30day = 0
|
||||
context = {
|
||||
'channels': channels_df.to_dict(orient='records'),
|
||||
'channels': [] if channels_df.empty else channels_df.sort_values(by=['cv_30day'], ascending=False).to_dict(orient='records'),
|
||||
'apy_7day': apy_7day,
|
||||
'apy_30day': apy_30day,
|
||||
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
|
||||
|
|
@ -463,6 +521,22 @@ def route(request):
|
|||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def routes(request):
|
||||
if request.method == 'GET':
|
||||
try:
|
||||
pubkey = request.GET.urlencode()[1:]
|
||||
context = {
|
||||
'payment_hash': pubkey,
|
||||
'route': PaymentHops.objects.filter(payment_hash__in=PaymentHops.objects.filter(node_pubkey=pubkey).order_by('-id').values_list('payment_hash')[:69]).annotate(ppm=Round((Sum('fee')/Sum('amt'))*1000000, output_field=IntegerField()))
|
||||
}
|
||||
return render(request, 'route.html', context)
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
return render(request, 'error.html', {'error': error})
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def peers(request):
|
||||
if request.method == 'GET':
|
||||
|
|
@ -494,27 +568,63 @@ def balances(request):
|
|||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def closures(request):
|
||||
if request.method == 'GET':
|
||||
closures_df = DataFrame.from_records(Closures.objects.all().values())
|
||||
if closures_df.empty:
|
||||
merged = DataFrame()
|
||||
else:
|
||||
channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias'))
|
||||
if channels_df.empty:
|
||||
merged = closures_df
|
||||
merged['alias'] = ''
|
||||
try:
|
||||
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
|
||||
pending_channels = stub.PendingChannels(ln.PendingChannelsRequest())
|
||||
channels = Channels.objects.all()
|
||||
pending_closed = None
|
||||
pending_force_closed = None
|
||||
waiting_for_close = None
|
||||
if pending_channels.pending_closing_channels:
|
||||
target_resp = pending_channels.pending_closing_channels
|
||||
pending_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance, 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
|
||||
if pending_channels.pending_force_closing_channels:
|
||||
target_resp = pending_channels.pending_force_closing_channels
|
||||
pending_force_closed = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type,'closing_txid':target_resp[i].closing_txid,'limbo_balance':target_resp[i].limbo_balance,'maturity_height':target_resp[i].maturity_height,'blocks_til_maturity':target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else find_next_block_maturity(target_resp[i]),'maturity_datetime':(datetime.now()+timedelta(minutes=(10*target_resp[i].blocks_til_maturity if target_resp[i].blocks_til_maturity > 0 else 10*find_next_block_maturity(target_resp[i]) )))} for i in range(0,len(target_resp))]
|
||||
if pending_channels.waiting_close_channels:
|
||||
target_resp = pending_channels.waiting_close_channels
|
||||
waiting_for_close = [{'chan_id':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].chan_id if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'alias':channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1])[0].alias if channels.filter(funding_txid=target_resp[i].channel.channel_point.split(':')[0], output_index=target_resp[i].channel.channel_point.split(':')[1]).exists() else None,'remote_node_pub':target_resp[i].channel.remote_node_pub,'channel_point':target_resp[i].channel.channel_point,'capacity':target_resp[i].channel.capacity,'local_balance':target_resp[i].channel.local_balance,'remote_balance':target_resp[i].channel.remote_balance,'local_chan_reserve_sat':target_resp[i].channel.local_chan_reserve_sat,'remote_chan_reserve_sat':target_resp[i].channel.remote_chan_reserve_sat,'initiator':target_resp[i].channel.initiator,'commitment_type':target_resp[i].channel.commitment_type, 'local_commit_fee_sat': target_resp[i].commitments.local_commit_fee_sat, 'limbo_balance':target_resp[i].limbo_balance, 'closing_txid':target_resp[i].closing_txid} for i in range(0,len(target_resp))]
|
||||
closures_df = DataFrame.from_records(Closures.objects.all().values())
|
||||
if closures_df.empty:
|
||||
merged = DataFrame()
|
||||
else:
|
||||
merged = merge(closures_df, channels_df, on='chan_id', how='left')
|
||||
merged['alias'] = merged['alias'].fillna('')
|
||||
context = {
|
||||
'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
|
||||
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
|
||||
'network_links': network_links(),
|
||||
'graph_links': graph_links()
|
||||
}
|
||||
return render(request, 'closures.html', context)
|
||||
channels_df = DataFrame.from_records(Channels.objects.all().values('chan_id', 'alias', 'closing_costs'))
|
||||
if channels_df.empty:
|
||||
merged = closures_df
|
||||
merged['alias'] = ''
|
||||
else:
|
||||
merged = merge(closures_df, channels_df, on='chan_id', how='left')
|
||||
merged['alias'] = merged['alias'].fillna('')
|
||||
merged['closing_costs'] = merged['closing_costs'].fillna('')
|
||||
context = {
|
||||
'pending_closed': pending_closed,
|
||||
'pending_force_closed': pending_force_closed,
|
||||
'waiting_for_close': waiting_for_close,
|
||||
'closures': [] if merged.empty else merged.sort_values(by=['close_height'], ascending=False).to_dict(orient='records'),
|
||||
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
|
||||
'network_links': network_links(),
|
||||
'graph_links': graph_links()
|
||||
}
|
||||
return render(request, 'closures.html', context)
|
||||
except Exception as e:
|
||||
try:
|
||||
error = str(e.code())
|
||||
except:
|
||||
error = str(e)
|
||||
return render(request, 'error.html', {'error': error})
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
def find_next_block_maturity(force_closing_channel):
|
||||
#print (f"{datetime.now().strftime('%c')} : {force_closing_channel=}")
|
||||
if force_closing_channel.blocks_til_maturity > 0:
|
||||
return force_closing_channel.blocks_til_maturity
|
||||
for pending_htlc in force_closing_channel.pending_htlcs:
|
||||
if pending_htlc.blocks_til_maturity > 0:
|
||||
#print (f"{datetime.now().strftime('%c')} : {pending_htlc=}")
|
||||
return pending_htlc.blocks_til_maturity
|
||||
return -1
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def towers(request):
|
||||
if request.method == 'GET':
|
||||
|
|
@ -626,6 +736,150 @@ def resolutions(request):
|
|||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def income(request):
|
||||
if request.method == 'GET':
|
||||
stub = lnrpc.LightningStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
|
||||
filter_90day = datetime.now() - timedelta(days=90)
|
||||
filter_30day = datetime.now() - timedelta(days=30)
|
||||
filter_7day = datetime.now() - timedelta(days=7)
|
||||
filter_1day = datetime.now() - timedelta(days=1)
|
||||
node_info = stub.GetInfo(ln.GetInfoRequest())
|
||||
channels = Channels.objects.all()
|
||||
payments = Payments.objects.filter(status=2)
|
||||
payments_90day = payments.filter(creation_date__gte=filter_90day)
|
||||
payments_30day = payments.filter(creation_date__gte=filter_30day)
|
||||
payments_7day = payments.filter(creation_date__gte=filter_7day)
|
||||
payments_1day = payments.filter(creation_date__gte=filter_1day)
|
||||
onchain_txs = Onchain.objects.all()
|
||||
onchain_txs_90day = onchain_txs.filter(time_stamp__gte=filter_90day)
|
||||
onchain_txs_30day = onchain_txs.filter(time_stamp__gte=filter_30day)
|
||||
onchain_txs_7day = onchain_txs.filter(time_stamp__gte=filter_7day)
|
||||
onchain_txs_1day = onchain_txs.filter(time_stamp__gte=filter_1day)
|
||||
closures = Closures.objects.all()
|
||||
closures_90day = closures.filter(close_height__gte=(node_info.block_height - 12960))
|
||||
closures_30day = closures.filter(close_height__gte=(node_info.block_height - 4320))
|
||||
closures_7day = closures.filter(close_height__gte=(node_info.block_height - 1008))
|
||||
closures_1day = closures.filter(close_height__gte=(node_info.block_height - 144))
|
||||
forwards = Forwards.objects.all()
|
||||
forwards_90day = forwards.filter(forward_date__gte=filter_90day)
|
||||
forwards_30day = forwards.filter(forward_date__gte=filter_30day)
|
||||
forwards_7day = forwards.filter(forward_date__gte=filter_7day)
|
||||
forwards_1day = forwards.filter(forward_date__gte=filter_1day)
|
||||
forward_count = forwards.count()
|
||||
forward_count_90day = forwards_90day.count()
|
||||
forward_count_30day = forwards_30day.count()
|
||||
forward_count_7day = forwards_7day.count()
|
||||
forward_count_1day = forwards_1day.count()
|
||||
forward_amount = 0 if forward_count == 0 else int(forwards.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
|
||||
forward_amount_90day = 0 if forward_count_90day == 0 else int(forwards_90day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
|
||||
forward_amount_30day = 0 if forward_count_30day == 0 else int(forwards_30day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
|
||||
forward_amount_7day = 0 if forward_count_7day == 0 else int(forwards_7day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
|
||||
forward_amount_1day = 0 if forward_count_1day == 0 else int(forwards_1day.aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/1000)
|
||||
total_revenue = 0 if forward_count == 0 else int(forwards.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_revenue_90day = 0 if forward_count_90day == 0 else int(forwards_90day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_revenue_30day = 0 if forward_count_30day == 0 else int(forwards_30day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_revenue_7day = 0 if forward_count_7day == 0 else int(forwards_7day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_revenue_1day = 0 if forward_count_1day == 0 else int(forwards_1day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_revenue_ppm = 0 if forward_amount == 0 else int(total_revenue/(forward_amount/1000000))
|
||||
total_revenue_ppm_90day = 0 if forward_amount_90day == 0 else int(total_revenue_90day/(forward_amount_90day/1000000))
|
||||
total_revenue_ppm_30day = 0 if forward_amount_30day == 0 else int(total_revenue_30day/(forward_amount_30day/1000000))
|
||||
total_revenue_ppm_7day = 0 if forward_amount_7day == 0 else int(total_revenue_7day/(forward_amount_7day/1000000))
|
||||
total_revenue_ppm_1day = 0 if forward_amount_1day == 0 else int(total_revenue_1day/(forward_amount_1day/1000000))
|
||||
total_sent = 0 if payments.count() == 0 else int(payments.aggregate(Sum('value'))['value__sum'])
|
||||
total_sent_90day = 0 if payments_90day.count() == 0 else int(payments_90day.aggregate(Sum('value'))['value__sum'])
|
||||
total_sent_30day = 0 if payments_30day.count() == 0 else int(payments_30day.aggregate(Sum('value'))['value__sum'])
|
||||
total_sent_7day = 0 if payments_7day.count() == 0 else int(payments_7day.aggregate(Sum('value'))['value__sum'])
|
||||
total_sent_1day = 0 if payments_1day.count() == 0 else int(payments_1day.aggregate(Sum('value'))['value__sum'])
|
||||
total_fees = 0 if payments.count() == 0 else int(payments.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_fees_90day = 0 if payments_90day.count() == 0 else int(payments_90day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_fees_30day = 0 if payments_30day.count() == 0 else int(payments_30day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_fees_7day = 0 if payments_7day.count() == 0 else int(payments_7day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_fees_1day = 0 if payments_1day.count() == 0 else int(payments_1day.aggregate(Sum('fee'))['fee__sum'])
|
||||
total_fees_ppm = 0 if total_sent == 0 else int(total_fees/(total_sent/1000000))
|
||||
total_fees_ppm_90day = 0 if total_sent_90day == 0 else int(total_fees_90day/(total_sent_90day/1000000))
|
||||
total_fees_ppm_30day = 0 if total_sent_30day == 0 else int(total_fees_30day/(total_sent_30day/1000000))
|
||||
total_fees_ppm_7day = 0 if total_sent_7day == 0 else int(total_fees_7day/(total_sent_7day/1000000))
|
||||
total_fees_ppm_1day = 0 if total_sent_1day == 0 else int(total_fees_1day/(total_sent_1day/1000000))
|
||||
onchain_costs = 0 if onchain_txs.count() == 0 else onchain_txs.aggregate(Sum('fee'))['fee__sum']
|
||||
onchain_costs_90day = 0 if onchain_txs_90day.count() == 0 else onchain_txs_90day.aggregate(Sum('fee'))['fee__sum']
|
||||
onchain_costs_30day = 0 if onchain_txs_30day.count() == 0 else onchain_txs_30day.aggregate(Sum('fee'))['fee__sum']
|
||||
onchain_costs_7day = 0 if onchain_txs_7day.count() == 0 else onchain_txs_7day.aggregate(Sum('fee'))['fee__sum']
|
||||
onchain_costs_1day = 0 if onchain_txs_1day.count() == 0 else onchain_txs_1day.aggregate(Sum('fee'))['fee__sum']
|
||||
close_fees = channels.filter(chan_id__in=closures.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures.exists() else 0
|
||||
close_fees_90day = channels.filter(chan_id__in=closures_90day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_90day.exists() else 0
|
||||
close_fees_30day = channels.filter(chan_id__in=closures_30day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_30day.exists() else 0
|
||||
close_fees_7day = channels.filter(chan_id__in=closures_7day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_7day.exists() else 0
|
||||
close_fees_1day = channels.filter(chan_id__in=closures_1day.values('chan_id')).aggregate(Sum('closing_costs'))['closing_costs__sum'] if closures_1day.exists() else 0
|
||||
onchain_costs += close_fees
|
||||
onchain_costs_90day += close_fees_90day
|
||||
onchain_costs_30day += close_fees_30day
|
||||
onchain_costs_7day += close_fees_7day
|
||||
onchain_costs_1day += close_fees_1day
|
||||
profits = int(total_revenue-total_fees-onchain_costs)
|
||||
profits_90day = int(total_revenue_90day-total_fees_90day-onchain_costs_90day)
|
||||
profits_30day = int(total_revenue_30day-total_fees_30day-onchain_costs_30day)
|
||||
profits_7day = int(total_revenue_7day-total_fees_7day-onchain_costs_7day)
|
||||
profits_1day = int(total_revenue_1day-total_fees_1day-onchain_costs_1day)
|
||||
context = {
|
||||
'node_info': node_info,
|
||||
'forward_count': forward_count,
|
||||
'forward_count_90day': forward_count_90day,
|
||||
'forward_count_30day': forward_count_30day,
|
||||
'forward_count_7day': forward_count_7day,
|
||||
'forward_count_1day': forward_count_1day,
|
||||
'forward_amount': forward_amount,
|
||||
'forward_amount_90day': forward_amount_90day,
|
||||
'forward_amount_30day': forward_amount_30day,
|
||||
'forward_amount_7day': forward_amount_7day,
|
||||
'forward_amount_1day': forward_amount_1day,
|
||||
'total_revenue': total_revenue,
|
||||
'total_revenue_90day': total_revenue_90day,
|
||||
'total_revenue_30day': total_revenue_30day,
|
||||
'total_revenue_7day': total_revenue_7day,
|
||||
'total_revenue_1day': total_revenue_1day,
|
||||
'total_fees': total_fees,
|
||||
'total_fees_90day': total_fees_90day,
|
||||
'total_fees_30day': total_fees_30day,
|
||||
'total_fees_7day': total_fees_7day,
|
||||
'total_fees_1day': total_fees_1day,
|
||||
'total_fees_ppm': total_fees_ppm,
|
||||
'total_fees_ppm_90day': total_fees_ppm_90day,
|
||||
'total_fees_ppm_30day': total_fees_ppm_30day,
|
||||
'total_fees_ppm_7day': total_fees_ppm_7day,
|
||||
'total_fees_ppm_1day': total_fees_ppm_1day,
|
||||
'onchain_costs': onchain_costs,
|
||||
'onchain_costs_90day': onchain_costs_90day,
|
||||
'onchain_costs_30day': onchain_costs_30day,
|
||||
'onchain_costs_7day': onchain_costs_7day,
|
||||
'onchain_costs_1day': onchain_costs_1day,
|
||||
'total_revenue_ppm': total_revenue_ppm,
|
||||
'total_revenue_ppm_90day': total_revenue_ppm_90day,
|
||||
'total_revenue_ppm_30day': total_revenue_ppm_30day,
|
||||
'total_revenue_ppm_7day': total_revenue_ppm_7day,
|
||||
'total_revenue_ppm_1day': total_revenue_ppm_1day,
|
||||
'profits': profits,
|
||||
'profits_90day': profits_90day,
|
||||
'profits_30day': profits_30day,
|
||||
'profits_7day': profits_7day,
|
||||
'profits_1day': profits_1day,
|
||||
'profits_ppm': 0 if forward_amount == 0 else int(profits/(forward_amount/1000000)),
|
||||
'profits_ppm_90day': 0 if forward_amount_90day == 0 else int(profits_90day/(forward_amount_90day/1000000)),
|
||||
'profits_ppm_30day': 0 if forward_amount_30day == 0 else int(profits_30day/(forward_amount_30day/1000000)),
|
||||
'profits_ppm_7day': 0 if forward_amount_7day == 0 else int(profits_7day/(forward_amount_7day/1000000)),
|
||||
'profits_ppm_1day': 0 if forward_amount_1day == 0 else int(profits_1day/(forward_amount_1day/1000000)),
|
||||
'percent_cost': 0 if total_revenue == 0 else int(((total_fees+onchain_costs)/total_revenue)*100),
|
||||
'percent_cost_90day': 0 if total_revenue_90day == 0 else int(((total_fees_90day+onchain_costs_90day)/total_revenue_90day)*100),
|
||||
'percent_cost_30day': 0 if total_revenue_30day == 0 else int(((total_fees_30day+onchain_costs_30day)/total_revenue_30day)*100),
|
||||
'percent_cost_7day': 0 if total_revenue_7day == 0 else int(((total_fees_7day+onchain_costs_7day)/total_revenue_7day)*100),
|
||||
'percent_cost_1day': 0 if total_revenue_1day == 0 else int(((total_fees_1day+onchain_costs_1day)/total_revenue_1day)*100),
|
||||
'network': 'testnet/' if LND_NETWORK == 'testnet' else '',
|
||||
'graph_links': graph_links()
|
||||
}
|
||||
return render(request, 'income.html', context)
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def channel(request):
|
||||
if request.method == 'GET':
|
||||
|
|
@ -638,8 +892,12 @@ def channel(request):
|
|||
payments_df = DataFrame.from_records(Payments.objects.filter(status=2).filter(chan_out=chan_id).filter(rebal_chan__isnull=False).annotate(ppm=Round((Sum('fee')*1000000)/Sum('value'), output_field=IntegerField())).values())
|
||||
invoices_df = DataFrame.from_records(Invoices.objects.filter(state=1).filter(chan_in=chan_id).filter(r_hash__in=Payments.objects.filter(status=2).filter(rebal_chan=chan_id)).values())
|
||||
channels_df = DataFrame.from_records(Channels.objects.filter(is_open=True).values())
|
||||
node_outbound = channels_df['local_balance'].sum()
|
||||
node_capacity = channels_df['capacity'].sum()
|
||||
if channels_df.empty:
|
||||
node_outbound = 0
|
||||
node_capacity = 0
|
||||
else:
|
||||
node_outbound = channels_df['local_balance'].sum()
|
||||
node_capacity = channels_df['capacity'].sum()
|
||||
channels_df = DataFrame.from_records(Channels.objects.filter(chan_id=chan_id).values())
|
||||
rebalancer_df = DataFrame.from_records(Rebalancer.objects.filter(last_hop_pubkey=channels_df['remote_pubkey'][0]).annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id').values())
|
||||
failed_htlc_df = DataFrame.from_records(FailedHTLCs.objects.filter(Q(chan_id_in=chan_id) | Q(chan_id_out=chan_id)).order_by('-id').values())
|
||||
|
|
@ -887,6 +1145,7 @@ def channel(request):
|
|||
channels_df['costs_30day'] = 0 if channels_df['rebal_in_30day'][0] == 0 or invoice_hashes_30d.empty == True else int(rebal_payments_df_30d.set_index('payment_hash', inplace=False).loc[invoice_hashes_30d[chan_id]]['fee'].sum())
|
||||
channels_df['costs_7day'] = 0 if channels_df['rebal_in_7day'][0] == 0 or invoice_hashes_7d.empty == True else int(rebal_payments_df_7d.set_index('payment_hash', inplace=False).loc[invoice_hashes_7d[chan_id]]['fee'].sum())
|
||||
channels_df['costs_1day'] = 0 if channels_df['rebal_in_1day'][0] == 0 or invoice_hashes_1d.empty == True else int(rebal_payments_df_1d.set_index('payment_hash', inplace=False).loc[invoice_hashes_1d[chan_id]]['fee'].sum())
|
||||
channels_df['costs'] += channels_df['closing_costs']
|
||||
channels_df['profits'] = channels_df['revenue'] - channels_df['costs']
|
||||
channels_df['profits_30day'] = channels_df['revenue_30day'] - channels_df['costs_30day']
|
||||
channels_df['profits_7day'] = channels_df['revenue_7day'] - channels_df['costs_7day']
|
||||
|
|
@ -1126,6 +1385,16 @@ def invoices(request):
|
|||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def rebalances(request):
|
||||
if request.method == 'GET':
|
||||
context = {
|
||||
'rebalances': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:150],
|
||||
}
|
||||
return render(request, 'rebalances.html', context)
|
||||
else:
|
||||
return redirect('home')
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def batch(request):
|
||||
if request.method == 'GET':
|
||||
|
|
@ -1302,12 +1571,16 @@ def rebalancing(request):
|
|||
eligible_df = enabled_df[enabled_df['is_active']==True][enabled_df['inbound_can']>=1][enabled_df['fee_check']<100]
|
||||
eligible_count = eligible_df.shape[0]
|
||||
enabled_count = enabled_df.shape[0]
|
||||
available_df = channels_df[channels_df['auto_rebalance']==False][channels_df['is_active']==True][channels_df['percent_outbound'] / channels_df['ar_out_target']>=1]
|
||||
available_count = available_df.shape[0]
|
||||
else:
|
||||
eligible_count = 0
|
||||
enabled_count = 0
|
||||
available_count = 0
|
||||
context = {
|
||||
'eligible_count': eligible_count,
|
||||
'enabled_count': enabled_count,
|
||||
'available_count': available_count,
|
||||
'channels': channels_df.to_dict(orient='records'),
|
||||
'rebalancer': Rebalancer.objects.all().annotate(ppm=Round((Sum('fee_limit')*1000000)/Sum('value'), output_field=IntegerField())).order_by('-id')[:20],
|
||||
'rebalancer_form': RebalancerForm,
|
||||
|
|
@ -1593,8 +1866,11 @@ def auto_rebalance(request):
|
|||
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
|
||||
db_percent_target.value = target_percent
|
||||
db_percent_target.save()
|
||||
Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(target_percent/100), output_field=IntegerField()))
|
||||
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
|
||||
if form.cleaned_data['targetallchannels']:
|
||||
Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(target_percent/100), output_field=IntegerField()))
|
||||
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
|
||||
else:
|
||||
messages.success(request, 'Updated auto rebalancer target amount in local settings: ' + str(target_percent))
|
||||
if form.cleaned_data['target_time'] is not None:
|
||||
target_time = form.cleaned_data['target_time']
|
||||
try:
|
||||
|
|
@ -1624,8 +1900,25 @@ def auto_rebalance(request):
|
|||
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
|
||||
db_outbound_target.value = outbound_percent
|
||||
db_outbound_target.save()
|
||||
Channels.objects.all().update(ar_out_target=int(outbound_percent))
|
||||
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
|
||||
if form.cleaned_data['targetallchannels']:
|
||||
Channels.objects.all().update(ar_out_target=int(outbound_percent))
|
||||
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
|
||||
else:
|
||||
messages.success(request, 'Updated auto rebalancer target outbound percent setting in local settings to: ' + str(outbound_percent))
|
||||
if form.cleaned_data['inbound_percent'] is not None:
|
||||
inbound_percent = int(form.cleaned_data['inbound_percent'])
|
||||
try:
|
||||
db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
|
||||
except:
|
||||
LocalSettings(key='AR-Inbound%', value='100').save()
|
||||
db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
|
||||
db_inbound_target.value = inbound_percent
|
||||
db_inbound_target.save()
|
||||
if form.cleaned_data['targetallchannels']:
|
||||
Channels.objects.all().update(ar_out_target=int(outbound_percent))
|
||||
messages.success(request, 'Updated auto rebalancer target inbound percent setting for all channels to: ' + str(inbound_percent))
|
||||
else:
|
||||
messages.success(request, 'Updated auto rebalancer target inbound percent setting in local settigs to: ' + str(inbound_percent))
|
||||
if form.cleaned_data['fee_rate'] is not None:
|
||||
fee_rate = form.cleaned_data['fee_rate']
|
||||
try:
|
||||
|
|
@ -1645,8 +1938,11 @@ def auto_rebalance(request):
|
|||
db_max_cost = LocalSettings.objects.get(key='AR-MaxCost%')
|
||||
db_max_cost.value = max_cost
|
||||
db_max_cost.save()
|
||||
Channels.objects.all().update(ar_max_cost=int(max_cost))
|
||||
messages.success(request, 'Updated auto rebalancer max cost setting to: ' + str(max_cost))
|
||||
if form.cleaned_data['targetallchannels']:
|
||||
Channels.objects.all().update(ar_max_cost=int(max_cost))
|
||||
messages.success(request, 'Updated auto rebalancer max cost setting for all channels to: ' + str(max_cost))
|
||||
else:
|
||||
messages.success(request, 'Updated auto rebalancer max cost setting in local settings to: ' + str(max_cost))
|
||||
if form.cleaned_data['autopilot'] is not None:
|
||||
autopilot = form.cleaned_data['autopilot']
|
||||
try:
|
||||
|
|
@ -1657,6 +1953,16 @@ def auto_rebalance(request):
|
|||
db_autopilot.value = autopilot
|
||||
db_autopilot.save()
|
||||
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
|
||||
if form.cleaned_data['autopilotdays'] is not None:
|
||||
autopilotdays = form.cleaned_data['autopilotdays']
|
||||
try:
|
||||
db_autopilotdays = LocalSettings.objects.get(key='AR-APDays')
|
||||
except:
|
||||
LocalSettings(key='AR-APDays', value='7').save()
|
||||
db_autopilotdays = LocalSettings.objects.get(key='AR-APDays')
|
||||
db_autopilotdays.value = autopilotdays
|
||||
db_autopilotdays.save()
|
||||
messages.success(request, 'Updated autopilot days setting to: ' + str(autopilotdays))
|
||||
if form.cleaned_data['variance'] is not None:
|
||||
variance = form.cleaned_data['variance']
|
||||
try:
|
||||
|
|
@ -1757,6 +2063,66 @@ def update_channel(request):
|
|||
db_channel.local_cltv = target
|
||||
db_channel.save()
|
||||
messages.success(request, 'CLTV for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(target))
|
||||
elif update_target == 10:
|
||||
db_channel.closing_costs = target
|
||||
db_channel.save()
|
||||
messages.success(request, 'Closing costs for channel ' + str(db_channel.alias) + ' (' + str(db_channel.chan_id) + ') updated to a value of: ' + str(db_channel.closing_costs))
|
||||
else:
|
||||
messages.error(request, 'Invalid target code. Please try again.')
|
||||
else:
|
||||
messages.error(request, 'Invalid Request. Please try again.')
|
||||
return redirect(request.META.get('HTTP_REFERER'))
|
||||
|
||||
@login_required(login_url='/lndg-admin/login/?next=/')
|
||||
def update_pending(request):
|
||||
if request.method == 'POST':
|
||||
form = UpdatePending(request.POST)
|
||||
if form.is_valid():
|
||||
funding_txid = form.cleaned_data['funding_txid']
|
||||
output_index = form.cleaned_data['output_index']
|
||||
target = form.cleaned_data['target']
|
||||
update_target = int(form.cleaned_data['update_target'])
|
||||
if PendingChannels.objects.filter(funding_txid=funding_txid, output_index=output_index).exists():
|
||||
pending_channel = PendingChannels.objects.filter(funding_txid=funding_txid, output_index=output_index)[0]
|
||||
else:
|
||||
pending_channel = PendingChannels(funding_txid=funding_txid, output_index=output_index)
|
||||
pending_channel.save()
|
||||
if update_target == 0:
|
||||
pending_channel.local_base_fee = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Base fee for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
|
||||
elif update_target == 1:
|
||||
pending_channel.local_fee_rate = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Fee rate for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
|
||||
elif update_target == 2:
|
||||
pending_channel.ar_amt_target = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto rebalancer target amount for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
|
||||
elif update_target == 3:
|
||||
pending_channel.ar_in_target = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto rebalancer inbound target for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
|
||||
elif update_target == 4:
|
||||
pending_channel.ar_out_target = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto rebalancer outbound target for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
|
||||
elif update_target == 5:
|
||||
pending_channel.auto_rebalance = True if pending_channel.auto_rebalance == False else False
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto rebalancer status for pending pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(pending_channel.auto_rebalance))
|
||||
elif update_target == 6:
|
||||
pending_channel.ar_max_cost = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto rebalancer max cost for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target) + '%')
|
||||
elif update_target == 8:
|
||||
pending_channel.auto_fees = True if pending_channel.auto_fees == False else False
|
||||
pending_channel.save()
|
||||
messages.success(request, 'Auto fees status for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(pending_channel.auto_fees))
|
||||
elif update_target == 9:
|
||||
pending_channel.local_cltv = target
|
||||
pending_channel.save()
|
||||
messages.success(request, 'CLTV for pending channel (' + str(funding_txid) + ') updated to a value of: ' + str(target))
|
||||
else:
|
||||
messages.error(request, 'Invalid target code. Please try again.')
|
||||
else:
|
||||
|
|
@ -1779,7 +2145,7 @@ def update_setting(request):
|
|||
db_percent_target = LocalSettings.objects.get(key='AR-Target%')
|
||||
db_percent_target.value = target_percent
|
||||
db_percent_target.save()
|
||||
messages.success(request, 'Updated auto rebalancer target amount for all channels to: ' + str(target_percent))
|
||||
messages.success(request, 'Updated auto rebalancer target amount to: ' + str(target_percent))
|
||||
elif key == 'AR-Time':
|
||||
target_time = int(value)
|
||||
try:
|
||||
|
|
@ -1809,7 +2175,17 @@ def update_setting(request):
|
|||
db_outbound_target = LocalSettings.objects.get(key='AR-Outbound%')
|
||||
db_outbound_target.value = outbound_percent
|
||||
db_outbound_target.save()
|
||||
messages.success(request, 'Updated auto rebalancer target outbound percent setting for all channels to: ' + str(outbound_percent))
|
||||
messages.success(request, 'Updated auto rebalancer target outbound percent setting: ' + str(outbound_percent))
|
||||
elif key == 'AR-Inbound%':
|
||||
inbound_percent = int(value)
|
||||
try:
|
||||
db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
|
||||
except:
|
||||
LocalSettings(key='AR-Inbound%', value='100').save()
|
||||
db_inbound_target = LocalSettings.objects.get(key='AR-Inbound%')
|
||||
db_inbound_target.value = inbound_percent
|
||||
db_inbound_target.save()
|
||||
messages.success(request, 'Updated auto rebalancer target inbound percent setting: ' + str(inbound_percent))
|
||||
elif key == 'AR-MaxFeeRate':
|
||||
fee_rate = int(value)
|
||||
try:
|
||||
|
|
@ -1840,6 +2216,16 @@ def update_setting(request):
|
|||
db_autopilot.value = autopilot
|
||||
db_autopilot.save()
|
||||
messages.success(request, 'Updated autopilot setting to: ' + str(autopilot))
|
||||
elif key == 'AR-APDays':
|
||||
apdays = int(value)
|
||||
try:
|
||||
db_apdays = LocalSettings.objects.get(key='AR-APDays')
|
||||
except:
|
||||
LocalSettings(key='AR-APDays', value='7').save()
|
||||
db_apdays = LocalSettings.objects.get(key='AR-APDays')
|
||||
db_apdays.value = apdays
|
||||
db_apdays.save()
|
||||
messages.success(request, 'Updated Autopilot Days setting to: ' + str(apdays))
|
||||
elif key == 'AR-Variance':
|
||||
variance = int(value)
|
||||
try:
|
||||
|
|
@ -2327,4 +2713,4 @@ def pending_channels(request):
|
|||
details_index = error.find('details =') + 11
|
||||
debug_error_index = error.find('debug_error_string =') - 3
|
||||
error_msg = error[details_index:debug_error_index]
|
||||
return Response({'error': 'Failed to get pending channels! Error: ' + error_msg})
|
||||
return Response({'error': 'Failed to get pending channels! Error: ' + error_msg})
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from django.contrib.auth import get_user_model
|
|||
from django.conf import settings
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
|
||||
def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug):
|
||||
def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted):
|
||||
#Generate a unique secret to be used for your django site
|
||||
secret = secrets.token_urlsafe(64)
|
||||
if whitenoise:
|
||||
|
|
@ -13,6 +13,12 @@ def write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenois
|
|||
'whitenoise.middleware.WhiteNoiseMiddleware',"""
|
||||
else:
|
||||
wnl = ''
|
||||
if csrftrusted:
|
||||
csrf = """
|
||||
CSRF_TRUSTED_ORIGINS = [%s]
|
||||
""" % (csrftrusted)
|
||||
else:
|
||||
csrf = ''
|
||||
settings_file = '''"""
|
||||
Django settings for lndg project.
|
||||
|
||||
|
|
@ -41,7 +47,7 @@ SECRET_KEY = '%s'
|
|||
DEBUG = %s
|
||||
|
||||
ALLOWED_HOSTS = ['%s']
|
||||
|
||||
%s
|
||||
LND_DIR_PATH = '%s'
|
||||
LND_NETWORK = '%s'
|
||||
LND_RPC_SERVER = '%s'
|
||||
|
|
@ -151,7 +157,7 @@ USE_TZ = False
|
|||
STATIC_URL = 'static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'gui/static/')
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
''' % (secret, debug, node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, wnl)
|
||||
''' % (secret, debug, node_ip, csrf, lnd_dir_path, lnd_network, lnd_rpc_server, wnl)
|
||||
try:
|
||||
f = open("lndg/settings.py", "x")
|
||||
f.close()
|
||||
|
|
@ -247,7 +253,9 @@ def main():
|
|||
parser.add_argument('-wn', '--whitenoise', help = 'Add whitenoise middleware (docker requirement for static files)', action='store_true')
|
||||
parser.add_argument('-d', '--docker', help = 'Single option for docker container setup (supervisord + whitenoise)', action='store_true')
|
||||
parser.add_argument('-dx', '--debug', help = 'Setup the django site in debug mode', action='store_true')
|
||||
parser.add_argument('-u', '--adminuser', help = 'Setup a custom admin username', default='lndg-admin')
|
||||
parser.add_argument('-pw', '--adminpw', help = 'Setup a custom admin password', default=None)
|
||||
parser.add_argument('-csrf', '--csrftrusted', help = 'Set trusted CSRF origins', default=None)
|
||||
args = parser.parse_args()
|
||||
node_ip = args.nodeip
|
||||
lnd_dir_path = args.lnddir
|
||||
|
|
@ -258,11 +266,13 @@ def main():
|
|||
whitenoise = args.whitenoise
|
||||
docker = args.docker
|
||||
debug = args.debug
|
||||
adminuser = args.adminuser
|
||||
adminpw = args.adminpw
|
||||
csrftrusted = args.csrftrusted
|
||||
if docker:
|
||||
setup_supervisord = True
|
||||
whitenoise = True
|
||||
write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug)
|
||||
write_settings(node_ip, lnd_dir_path, lnd_network, lnd_rpc_server, whitenoise, debug, csrftrusted)
|
||||
if setup_supervisord:
|
||||
print('Supervisord setup requested...')
|
||||
write_supervisord_settings(sduser)
|
||||
|
|
@ -300,8 +310,8 @@ def main():
|
|||
if get_user_model().objects.count() == 0:
|
||||
print('Setting up initial user...')
|
||||
try:
|
||||
call_command('createsuperuser', username='lndg-admin', email='admin@lndg.local', interactive=False)
|
||||
admin = get_user_model().objects.get(username='lndg-admin')
|
||||
call_command('createsuperuser', username=adminuser, email='admin@lndg.local', interactive=False)
|
||||
admin = get_user_model().objects.get(username=adminuser)
|
||||
login_pw = secrets.token_urlsafe(16) if adminpw is None else adminpw
|
||||
admin.set_password(login_pw)
|
||||
admin.save()
|
||||
|
|
|
|||
255
jobs.py
255
jobs.py
|
|
@ -1,5 +1,5 @@
|
|||
import django
|
||||
from django.db.models import Max
|
||||
from django.db.models import Max, Min
|
||||
from datetime import datetime, timedelta
|
||||
from gui.lnd_deps import lightning_pb2 as ln
|
||||
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
|
||||
|
|
@ -9,17 +9,24 @@ from gui.lnd_deps.lnd_connect import lnd_connect
|
|||
from lndg import settings
|
||||
from os import environ
|
||||
from pandas import DataFrame
|
||||
from requests import get
|
||||
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
|
||||
django.setup()
|
||||
from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees
|
||||
from gui.models import Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, LocalSettings, FailedHTLCs, Autofees, PendingChannels
|
||||
from lndg.settings import LND_NETWORK
|
||||
|
||||
def update_payments(stub):
|
||||
#Remove anything in-flight so we can get most up to date status
|
||||
Payments.objects.filter(status=1).delete()
|
||||
#Get the number of records in the database currently
|
||||
last_index = 0 if Payments.objects.aggregate(Max('index'))['index__max'] == None else Payments.objects.aggregate(Max('index'))['index__max']
|
||||
payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
|
||||
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
|
||||
inflight_payments = Payments.objects.filter(status=1).order_by('index')
|
||||
for payment in inflight_payments:
|
||||
payment_data = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=payment.index-1, max_payments=1)).payments
|
||||
if len(payment_data) > 0 and payment.payment_hash == payment_data[0].payment_hash:
|
||||
update_payment(stub, payment_data[0], self_pubkey)
|
||||
else:
|
||||
payment.status = 3
|
||||
payment.save()
|
||||
last_index = Payments.objects.aggregate(Max('index'))['index__max'] if Payments.objects.exists() else 0
|
||||
payments = stub.ListPayments(ln.ListPaymentsRequest(include_incomplete=True, index_offset=last_index, max_payments=100)).payments
|
||||
for payment in payments:
|
||||
try:
|
||||
new_payment = Payments(creation_date=datetime.fromtimestamp(payment.creation_date), payment_hash=payment.payment_hash, value=round(payment.value_msat/1000, 3), fee=round(payment.fee_msat/1000, 3), status=payment.status, index=payment.payment_index)
|
||||
|
|
@ -57,85 +64,108 @@ def update_payments(stub):
|
|||
new_payment.save()
|
||||
except:
|
||||
#Error inserting, try to update instead
|
||||
db_payment = Payments.objects.filter(payment_hash=payment.payment_hash)[0]
|
||||
db_payment.creation_date = datetime.fromtimestamp(payment.creation_date)
|
||||
db_payment.value = round(payment.value_msat/1000, 3)
|
||||
db_payment.fee = round(payment.fee_msat/1000, 3)
|
||||
db_payment.status = payment.status
|
||||
db_payment.index = payment.payment_index
|
||||
db_payment.save()
|
||||
if payment.status == 2:
|
||||
for attempt in payment.htlcs:
|
||||
if attempt.status == 1:
|
||||
PaymentHops.objects.filter(payment_hash=db_payment).delete()
|
||||
hops = attempt.route.hops
|
||||
hop_count = 0
|
||||
cost_to = 0
|
||||
total_hops = len(hops)
|
||||
for hop in hops:
|
||||
hop_count += 1
|
||||
try:
|
||||
alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=hop.pub_key, include_channels=False)).node.alias
|
||||
except:
|
||||
alias = ''
|
||||
fee = hop.fee_msat/1000
|
||||
PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save()
|
||||
cost_to += fee
|
||||
if hop_count == 1:
|
||||
if db_payment.chan_out is None:
|
||||
db_payment.chan_out = hop.chan_id
|
||||
db_payment.chan_out_alias = alias
|
||||
else:
|
||||
db_payment.chan_out = 'MPP'
|
||||
db_payment.chan_out_alias = 'MPP'
|
||||
if hop_count == total_hops and 5482373484 in hop.custom_records and db_payment.keysend_preimage is None:
|
||||
records = hop.custom_records
|
||||
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
||||
db_payment.keysend_preimage = records[5482373484].hex()
|
||||
db_payment.message = message
|
||||
if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None:
|
||||
db_payment.rebal_chan = hop.chan_id
|
||||
db_payment.save()
|
||||
update_payment(stub, payment, self_pubkey)
|
||||
|
||||
def update_payment(stub, payment, self_pubkey):
|
||||
db_payment = Payments.objects.filter(payment_hash=payment.payment_hash)[0]
|
||||
db_payment.creation_date = datetime.fromtimestamp(payment.creation_date)
|
||||
db_payment.value = round(payment.value_msat/1000, 3)
|
||||
db_payment.fee = round(payment.fee_msat/1000, 3)
|
||||
db_payment.status = payment.status
|
||||
db_payment.index = payment.payment_index
|
||||
db_payment.save()
|
||||
if payment.status == 2:
|
||||
PaymentHops.objects.filter(payment_hash=db_payment).delete()
|
||||
for attempt in payment.htlcs:
|
||||
if attempt.status == 1:
|
||||
hops = attempt.route.hops
|
||||
hop_count = 0
|
||||
cost_to = 0
|
||||
total_hops = len(hops)
|
||||
for hop in hops:
|
||||
hop_count += 1
|
||||
try:
|
||||
alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=hop.pub_key, include_channels=False)).node.alias
|
||||
except:
|
||||
alias = ''
|
||||
fee = hop.fee_msat/1000
|
||||
PaymentHops(payment_hash=db_payment, attempt_id=attempt.attempt_id, step=hop_count, chan_id=hop.chan_id, alias=alias, chan_capacity=hop.chan_capacity, node_pubkey=hop.pub_key, amt=round(hop.amt_to_forward_msat/1000, 3), fee=round(fee, 3), cost_to=round(cost_to, 3)).save()
|
||||
cost_to += fee
|
||||
if hop_count == 1:
|
||||
if db_payment.chan_out is None:
|
||||
db_payment.chan_out = hop.chan_id
|
||||
db_payment.chan_out_alias = alias
|
||||
else:
|
||||
db_payment.chan_out = 'MPP'
|
||||
db_payment.chan_out_alias = 'MPP'
|
||||
if hop_count == total_hops and 5482373484 in hop.custom_records and db_payment.keysend_preimage is None:
|
||||
records = hop.custom_records
|
||||
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
||||
db_payment.keysend_preimage = records[5482373484].hex()
|
||||
db_payment.message = message
|
||||
if hop_count == total_hops and hop.pub_key == self_pubkey and db_payment.rebal_chan is None:
|
||||
db_payment.rebal_chan = hop.chan_id
|
||||
db_payment.save()
|
||||
|
||||
def update_invoices(stub):
|
||||
#Remove anything open so we can get most up to date status
|
||||
Invoices.objects.filter(state=0).delete()
|
||||
last_index = 0 if Invoices.objects.aggregate(Max('index'))['index__max'] == None else Invoices.objects.aggregate(Max('index'))['index__max']
|
||||
open_invoices = Invoices.objects.filter(state=0).order_by('index')
|
||||
for open_invoice in open_invoices:
|
||||
invoice_data = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=open_invoice.index-1, num_max_invoices=1)).invoices
|
||||
if len(invoice_data) > 0 and open_invoice.r_hash == invoice_data[0].r_hash.hex():
|
||||
update_invoice(stub, invoice_data[0], open_invoice)
|
||||
else:
|
||||
open_invoice.state = 2
|
||||
open_invoice.save()
|
||||
last_index = Invoices.objects.aggregate(Max('index'))['index__max'] if Invoices.objects.exists() else 0
|
||||
invoices = stub.ListInvoices(ln.ListInvoiceRequest(index_offset=last_index, num_max_invoices=100)).invoices
|
||||
for invoice in invoices:
|
||||
if invoice.state == 1:
|
||||
if len(invoice.htlcs) > 0:
|
||||
chan_in_id = invoice.htlcs[0].chan_id
|
||||
alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
|
||||
records = invoice.htlcs[0].custom_records
|
||||
keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
|
||||
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
||||
if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
|
||||
signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
|
||||
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
|
||||
try:
|
||||
valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
|
||||
except:
|
||||
print('Unable to validate signature on invoice: ' + invoice.r_hash.hex())
|
||||
valid = False
|
||||
sender = records[34349339].hex() if valid == True else None
|
||||
try:
|
||||
sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
|
||||
except:
|
||||
sender_alias = None
|
||||
else:
|
||||
sender = None
|
||||
db_invoice = Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index)
|
||||
db_invoice.save()
|
||||
update_invoice(stub, invoice, db_invoice)
|
||||
|
||||
def update_invoice(stub, invoice, db_invoice):
|
||||
if invoice.state == 1:
|
||||
if len(invoice.htlcs) > 0:
|
||||
chan_in_id = invoice.htlcs[0].chan_id
|
||||
alias = Channels.objects.filter(chan_id=chan_in_id)[0].alias if Channels.objects.filter(chan_id=chan_in_id).exists() else None
|
||||
records = invoice.htlcs[0].custom_records
|
||||
keysend_preimage = records[5482373484].hex() if 5482373484 in records else None
|
||||
message = records[34349334].decode('utf-8', errors='ignore')[:1000] if 34349334 in records else None
|
||||
if 34349337 in records and 34349339 in records and 34349343 in records and 34349334 in records:
|
||||
signerstub = lnsigner.SignerStub(lnd_connect(settings.LND_DIR_PATH, settings.LND_NETWORK, settings.LND_RPC_SERVER))
|
||||
self_pubkey = stub.GetInfo(ln.GetInfoRequest()).identity_pubkey
|
||||
try:
|
||||
valid = signerstub.VerifyMessage(lns.VerifyMessageReq(msg=(records[34349339]+bytes.fromhex(self_pubkey)+records[34349343]+records[34349334]), signature=records[34349337], pubkey=records[34349339])).valid
|
||||
except:
|
||||
print('Unable to validate signature on invoice: ' + invoice.r_hash.hex())
|
||||
valid = False
|
||||
sender = records[34349339].hex() if valid == True else None
|
||||
try:
|
||||
sender_alias = stub.GetNodeInfo(ln.NodeInfoRequest(pub_key=sender, include_channels=False)).node.alias if sender != None else None
|
||||
except:
|
||||
sender_alias = None
|
||||
else:
|
||||
chan_in_id = None
|
||||
alias = None
|
||||
keysend_preimage = None
|
||||
message = None
|
||||
sender = None
|
||||
sender_alias = None
|
||||
Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), settle_date=datetime.fromtimestamp(invoice.settle_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, chan_in=chan_in_id, chan_in_alias=alias, keysend_preimage=keysend_preimage, message=message, sender=sender, sender_alias=sender_alias, index=invoice.add_index).save()
|
||||
else:
|
||||
Invoices(creation_date=datetime.fromtimestamp(invoice.creation_date), r_hash=invoice.r_hash.hex(), value=round(invoice.value_msat/1000, 3), amt_paid=invoice.amt_paid_sat, state=invoice.state, index=invoice.add_index).save()
|
||||
chan_in_id = None
|
||||
alias = None
|
||||
keysend_preimage = None
|
||||
message = None
|
||||
sender = None
|
||||
sender_alias = None
|
||||
db_invoice.state = invoice.state
|
||||
db_invoice.amt_paid = invoice.amt_paid_sat
|
||||
db_invoice.settle_date = datetime.fromtimestamp(invoice.settle_date)
|
||||
db_invoice.chan_in = chan_in_id
|
||||
db_invoice.chan_in_alias = alias
|
||||
db_invoice.keysend_preimage = keysend_preimage
|
||||
db_invoice.message = message
|
||||
db_invoice.sender = sender
|
||||
db_invoice.sender_alias = sender_alias
|
||||
else:
|
||||
db_invoice.state = invoice.state
|
||||
db_invoice.save()
|
||||
|
||||
def update_forwards(stub):
|
||||
records = Forwards.objects.count()
|
||||
|
|
@ -156,6 +186,7 @@ def update_channels(stub):
|
|||
if Channels.objects.filter(chan_id=channel.chan_id).exists():
|
||||
#Update the channel record with the most current data
|
||||
db_channel = Channels.objects.filter(chan_id=channel.chan_id)[0]
|
||||
pending_channel = None
|
||||
else:
|
||||
#Create a record for this new channel
|
||||
try:
|
||||
|
|
@ -173,6 +204,7 @@ def update_channels(stub):
|
|||
db_channel.output_index = index
|
||||
db_channel.capacity = channel.capacity
|
||||
db_channel.private = channel.private
|
||||
pending_channel = PendingChannels.objects.filter(funding_txid=txid, output_index=index)[0] if PendingChannels.objects.filter(funding_txid=txid, output_index=index).exists() else None
|
||||
try:
|
||||
chan_data = stub.GetChanInfo(ln.ChanInfoRequest(chan_id=channel.chan_id))
|
||||
if chan_data.node1_pub == channel.remote_pubkey:
|
||||
|
|
@ -239,6 +271,33 @@ def update_channels(stub):
|
|||
db_channel.pending_outbound = pending_out
|
||||
db_channel.pending_inbound = pending_in
|
||||
db_channel.htlc_count = htlc_counter
|
||||
if pending_channel:
|
||||
if pending_channel.local_base_fee or pending_channel.local_fee_rate or pending_channel.local_cltv:
|
||||
base_fee = pending_channel.local_base_fee if pending_channel.local_base_fee else db_channel.local_base_fee
|
||||
fee_rate = pending_channel.local_fee_rate if pending_channel.local_fee_rate else db_channel.local_fee_rate
|
||||
cltv = pending_channel.local_cltv if pending_channel.local_cltv else db_channel.local_cltv
|
||||
channel_point = ln.ChannelPoint()
|
||||
channel_point.funding_txid_bytes = bytes.fromhex(db_channel.funding_txid)
|
||||
channel_point.funding_txid_str = db_channel.funding_txid
|
||||
channel_point.output_index = int(db_channel.output_index)
|
||||
stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=base_fee, fee_rate=(fee_rate/1000000), time_lock_delta=cltv))
|
||||
db_channel.local_base_fee = base_fee
|
||||
db_channel.local_fee_rate = fee_rate
|
||||
db_channel.local_cltv = cltv
|
||||
db_channel.fees_updated = datetime.now()
|
||||
if pending_channel.auto_rebalance:
|
||||
db_channel.auto_rebalance = pending_channel.auto_rebalance
|
||||
if pending_channel.ar_amt_target:
|
||||
db_channel.ar_amt_target = pending_channel.ar_amt_target
|
||||
if pending_channel.ar_in_target:
|
||||
db_channel.ar_in_target = pending_channel.ar_in_target
|
||||
if pending_channel.ar_out_target:
|
||||
db_channel.ar_out_target = pending_channel.ar_out_target
|
||||
if pending_channel.ar_max_cost:
|
||||
db_channel.ar_max_cost = pending_channel.ar_max_cost
|
||||
if pending_channel.auto_fees:
|
||||
db_channel.auto_fees = pending_channel.auto_fees
|
||||
pending_channel.delete()
|
||||
db_channel.save()
|
||||
counter += 1
|
||||
chan_list.append(channel.chan_id)
|
||||
|
|
@ -293,6 +352,24 @@ def update_onchain(stub):
|
|||
for tx in onchain_txs:
|
||||
Onchain(tx_hash=tx.tx_hash, time_stamp=datetime.fromtimestamp(tx.time_stamp), amount=tx.amount, fee=tx.total_fees, block_hash=tx.block_hash, block_height=tx.block_height, label=tx.label[:100]).save()
|
||||
|
||||
def network_links():
|
||||
if LocalSettings.objects.filter(key='GUI-NetLinks').exists():
|
||||
network_links = str(LocalSettings.objects.filter(key='GUI-NetLinks')[0].value)
|
||||
else:
|
||||
LocalSettings(key='GUI-NetLinks', value='https://mempool.space').save()
|
||||
network_links = 'https://mempool.space'
|
||||
return network_links
|
||||
|
||||
def get_tx_fees(txid):
|
||||
base_url = network_links() + ('/testnet' if LND_NETWORK == 'testnet' else '') + '/api/tx/'
|
||||
try:
|
||||
request_data = get(base_url + txid).json()
|
||||
fee = request_data['fee']
|
||||
except Exception as e:
|
||||
print('Error getting closure fees for ', txid, ':', str(e))
|
||||
fee = 0
|
||||
return fee
|
||||
|
||||
def update_closures(stub):
|
||||
closures = stub.ClosedChannels(ln.ClosedChannelsRequest()).channels
|
||||
if len(closures) > Closures.objects.all().count():
|
||||
|
|
@ -301,8 +378,10 @@ def update_closures(stub):
|
|||
for closure in closures:
|
||||
counter += 1
|
||||
if counter > skip:
|
||||
channel = Channels.objects.filter(chan_id=closure.chan_id)[0] if Channels.objects.filter(chan_id=closure.chan_id).exists() else None
|
||||
resolution_count = len(closure.resolutions)
|
||||
txid, index = closure.channel_point.split(':')
|
||||
closing_costs = get_tx_fees(closure.closing_tx_hash) if closure.open_initiator == 1 else 0
|
||||
db_closure = Closures(chan_id=closure.chan_id, funding_txid=txid, funding_index=index, closing_tx=closure.closing_tx_hash, remote_pubkey=closure.remote_pubkey, capacity=closure.capacity, close_height=closure.close_height, settled_balance=closure.settled_balance, time_locked_balance=closure.time_locked_balance, close_type=closure.close_type, open_initiator=closure.open_initiator, close_initiator=closure.close_initiator, resolution_count=resolution_count)
|
||||
try:
|
||||
db_closure.save()
|
||||
|
|
@ -313,7 +392,12 @@ def update_closures(stub):
|
|||
if resolution_count > 0:
|
||||
Resolutions.objects.filter(chan_id=closure.chan_id).delete()
|
||||
for resolution in closure.resolutions:
|
||||
if resolution.resolution_type != 2:
|
||||
closing_costs += get_tx_fees(resolution.sweep_txid)
|
||||
Resolutions(chan_id=closure.chan_id, resolution_type=resolution.resolution_type, outcome=resolution.outcome, outpoint_tx=resolution.outpoint.txid_str, outpoint_index=resolution.outpoint.output_index, amount_sat=resolution.amount_sat, sweep_txid=resolution.sweep_txid).save()
|
||||
if channel:
|
||||
channel.closing_costs = closing_costs
|
||||
channel.save()
|
||||
|
||||
def reconnect_peers(stub):
|
||||
inactive_peers = Channels.objects.filter(is_open=True, is_active=False, private=False).values_list('remote_pubkey', flat=True).distinct()
|
||||
|
|
@ -336,9 +420,16 @@ def reconnect_peers(stub):
|
|||
print('Unable to find node info on graph, using last known value')
|
||||
host = peer.address
|
||||
address = ln.LightningAddress(pubkey=inactive_peer, host=host)
|
||||
stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=True, timeout=5))
|
||||
peer.last_reconnected = datetime.now()
|
||||
peer.save()
|
||||
try:
|
||||
stub.ConnectPeer(request = ln.ConnectPeerRequest(addr=address, perm=True, timeout=5))
|
||||
peer.last_reconnected = datetime.now()
|
||||
peer.save()
|
||||
except Exception as e:
|
||||
error = str(e)
|
||||
details_index = error.find('details =') + 11
|
||||
debug_error_index = error.find('debug_error_string =') - 3
|
||||
error_msg = error[details_index:debug_error_index]
|
||||
print (f"{datetime.now().strftime('%c')} : Error reconnecting {inactive_peer=} {error_msg=}")
|
||||
|
||||
def clean_payments(stub):
|
||||
if LocalSettings.objects.filter(key='LND-CleanPayments').exists():
|
||||
|
|
@ -365,11 +456,11 @@ def clean_payments(stub):
|
|||
details_index = error.find('details =') + 11
|
||||
debug_error_index = error.find('debug_error_string =') - 3
|
||||
error_msg = error[details_index:debug_error_index]
|
||||
print('Error occured when cleaning payment: ' + payment.payment_hash)
|
||||
print('Error: ' + error_msg)
|
||||
print (f"{datetime.now().strftime('%c')} : Error {payment.index=} {payment.status=} {payment.payment_hash=} {error_msg=}")
|
||||
finally:
|
||||
payment.cleaned = True
|
||||
payment.save()
|
||||
print (f"{datetime.now().strftime('%c')} : Cleaned {payment.index=} {payment.status=} {payment.cleaned=} {payment.payment_hash=}")
|
||||
|
||||
def auto_fees(stub):
|
||||
if LocalSettings.objects.filter(key='AF-Enabled').exists():
|
||||
|
|
|
|||
2
nginx.sh
2
nginx.sh
|
|
@ -87,6 +87,8 @@ EOF
|
|||
|
||||
function setup_nginx() {
|
||||
cat << EOF > /etc/nginx/sites-enabled/lndg
|
||||
user $INSTALL_USER
|
||||
|
||||
upstream django {
|
||||
server unix://$HOME_DIR/lndg/lndg.sock; # for a file socket
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@ def run_rebalancer(rebalance):
|
|||
unknown_error.save()
|
||||
auto_rebalance_channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(percent_outbound=((Sum('local_balance')+Sum('pending_outbound'))*100)/Sum('capacity')).annotate(inbound_can=(((Sum('remote_balance')+Sum('pending_inbound'))*100)/Sum('capacity'))/Sum('ar_in_target'))
|
||||
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).exclude(remote_pubkey=rebalance.last_hop_pubkey).values_list('chan_id', flat=True))
|
||||
if len(outbound_cans) == 0:
|
||||
if len(outbound_cans) == 0 and rebalance.manual == False:
|
||||
print ('No outbound_cans')
|
||||
return None
|
||||
elif str(outbound_cans).replace('\'', '') != rebalance.outgoing_chan_ids and rebalance.manual == False:
|
||||
rebalance.outgoing_chan_ids = str(outbound_cans).replace('\'', '')
|
||||
|
|
@ -34,7 +35,9 @@ def run_rebalancer(rebalance):
|
|||
chan_ids = json.loads(rebalance.outgoing_chan_ids)
|
||||
timeout = rebalance.duration * 60
|
||||
invoice_response = stub.AddInvoice(ln.Invoice(value=rebalance.value, expiry=timeout))
|
||||
#print('Rebalance for:', rebalance.target_alias, ' : ', rebalance.last_hop_pubkey, ' Amount:', rebalance.value, ' Duration:', rebalance.duration, ' via:', chan_ids )
|
||||
for payment_response in routerstub.SendPaymentV2(lnr.SendPaymentRequest(payment_request=str(invoice_response.payment_request), fee_limit_msat=int(rebalance.fee_limit*1000), outgoing_chan_ids=chan_ids, last_hop_pubkey=bytes.fromhex(rebalance.last_hop_pubkey), timeout_seconds=(timeout-5), allow_self_payment=True), timeout=(timeout+60)):
|
||||
#print ('Payment Response:', payment_response.status, ' Reason:', payment_response.failure_reason, 'Payment Hash :', payment_response.payment_hash )
|
||||
if payment_response.status == 1 and rebalance.status == 0:
|
||||
#IN-FLIGHT
|
||||
rebalance.payment_hash = payment_response.payment_hash
|
||||
|
|
@ -43,6 +46,7 @@ def run_rebalancer(rebalance):
|
|||
elif payment_response.status == 2:
|
||||
#SUCCESSFUL
|
||||
rebalance.status = 2
|
||||
rebalance.fees_paid = payment_response.fee_msat/1000
|
||||
successful_out = payment_response.htlcs[0].route.hops[0].pub_key
|
||||
elif payment_response.status == 3:
|
||||
#FAILURE
|
||||
|
|
@ -64,6 +68,7 @@ def run_rebalancer(rebalance):
|
|||
elif payment_response.status == 0:
|
||||
rebalance.status = 400
|
||||
except Exception as e:
|
||||
#print('Exception: ', str(e))
|
||||
if str(e.code()) == 'StatusCode.DEADLINE_EXCEEDED':
|
||||
rebalance.status = 408
|
||||
else:
|
||||
|
|
@ -113,6 +118,8 @@ def auto_schedule():
|
|||
if len(auto_rebalance_channels) > 0:
|
||||
if not LocalSettings.objects.filter(key='AR-Outbound%').exists():
|
||||
LocalSettings(key='AR-Outbound%', value='75').save()
|
||||
if not LocalSettings.objects.filter(key='AR-Inbound%').exists():
|
||||
LocalSettings(key='AR-Inbound%', value='100').save()
|
||||
outbound_cans = list(auto_rebalance_channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
|
||||
inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1)
|
||||
if len(inbound_cans) > 0 and len(outbound_cans) > 0:
|
||||
|
|
@ -165,46 +172,61 @@ def auto_enable():
|
|||
else:
|
||||
LocalSettings(key='AR-Autopilot', value='0').save()
|
||||
enabled = 0
|
||||
if LocalSettings.objects.filter(key='AR-APDays').exists():
|
||||
apdays = int(LocalSettings.objects.filter(key='AR-APDays')[0].value)
|
||||
else:
|
||||
LocalSettings(key='AR-APDays', value='7').save()
|
||||
apdays = 7
|
||||
if enabled == 1:
|
||||
channels = Channels.objects.filter(is_active=True, is_open=True, private=False).annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity'))
|
||||
filter_7day = datetime.now() - timedelta(days=7)
|
||||
forwards = Forwards.objects.filter(forward_date__gte=filter_7day)
|
||||
lookup_channels=Channels.objects.filter(is_active=True, is_open=True, private=False)
|
||||
channels = lookup_channels.values('remote_pubkey').annotate(outbound_percent=((Sum('local_balance')+Sum('pending_outbound'))*1000)/Sum('capacity')).annotate(inbound_percent=((Sum('remote_balance')+Sum('pending_inbound'))*1000)/Sum('capacity')).order_by()
|
||||
filter_day = datetime.now() - timedelta(days=apdays)
|
||||
forwards = Forwards.objects.filter(forward_date__gte=filter_day)
|
||||
for channel in channels:
|
||||
outbound_percent = int(round(channel.outbound_percent/10, 0))
|
||||
inbound_percent = int(round(channel.inbound_percent/10, 0))
|
||||
routed_in_7day = forwards.filter(chan_id_in=channel.chan_id).count()
|
||||
routed_out_7day = forwards.filter(chan_id_out=channel.chan_id).count()
|
||||
i7D = 0 if routed_in_7day == 0 else int(forwards.filter(chan_id_in=channel.chan_id).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
|
||||
o7D = 0 if routed_out_7day == 0 else int(forwards.filter(chan_id_out=channel.chan_id).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
|
||||
if o7D > (i7D*1.10) and outbound_percent > 75:
|
||||
#print('Case 1: Pass')
|
||||
pass
|
||||
elif o7D > (i7D*1.10) and inbound_percent > 75 and channel.auto_rebalance == False:
|
||||
#print('Case 2: Enable AR - o7D > i7D AND Inbound Liq > 75%')
|
||||
channel.auto_rebalance = True
|
||||
channel.save()
|
||||
Autopilot(chan_id=channel.chan_id, peer_alias=channel.alias, setting='Enabled', old_value=0, new_value=1).save()
|
||||
elif o7D < (i7D*1.10) and outbound_percent > 75 and channel.auto_rebalance == True:
|
||||
#print('Case 3: Disable AR - o7D < i7D AND Outbound Liq > 75%')
|
||||
channel.auto_rebalance = False
|
||||
channel.save()
|
||||
Autopilot(chan_id=channel.chan_id, peer_alias=channel.alias, setting='Enabled', old_value=1, new_value=0).save()
|
||||
elif o7D < (i7D*1.10) and inbound_percent > 75:
|
||||
#print('Case 4: Pass')
|
||||
pass
|
||||
else:
|
||||
#print('Case 5: Pass')
|
||||
pass
|
||||
outbound_percent = int(round(channel['outbound_percent']/10, 0))
|
||||
inbound_percent = int(round(channel['inbound_percent']/10, 0))
|
||||
chan_list = lookup_channels.filter(remote_pubkey=channel['remote_pubkey']).values('chan_id')
|
||||
routed_in_apday = forwards.filter(chan_id_in__in=chan_list).count()
|
||||
routed_out_apday = forwards.filter(chan_id_out__in=chan_list).count()
|
||||
iapD = 0 if routed_in_apday == 0 else int(forwards.filter(chan_id_in__in=chan_list).aggregate(Sum('amt_in_msat'))['amt_in_msat__sum']/10000000)/100
|
||||
oapD = 0 if routed_out_apday == 0 else int(forwards.filter(chan_id_out__in=chan_list).aggregate(Sum('amt_out_msat'))['amt_out_msat__sum']/10000000)/100
|
||||
|
||||
for peer_channel in lookup_channels.filter(chan_id__in=chan_list):
|
||||
#print('Processing: ', peer_channel.alias, ' : ', peer_channel.chan_id, ' : ', oapD, " : ", iapD, ' : ', outbound_percent, ' : ', inbound_percent)
|
||||
|
||||
if oapD > (iapD*1.10) and outbound_percent > 75:
|
||||
#print('Case 1: Pass')
|
||||
pass
|
||||
elif oapD > (iapD*1.10) and inbound_percent > 75 and peer_channel.auto_rebalance == False:
|
||||
#print('Case 2: Enable AR - o7D > i7D AND Inbound Liq > 75%')
|
||||
peer_channel.auto_rebalance = True
|
||||
peer_channel.save()
|
||||
Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=0, new_value=1).save()
|
||||
print('Auto Pilot Enabled: ', peer_channel.alias, ' : ', peer_channel.chan_id , ' Out: ', oapD, ' In: ', iapD)
|
||||
elif oapD < (iapD*1.10) and outbound_percent > 75 and peer_channel.auto_rebalance == True:
|
||||
#print('Case 3: Disable AR - o7D < i7D AND Outbound Liq > 75%')
|
||||
peer_channel.auto_rebalance = False
|
||||
peer_channel.save()
|
||||
Autopilot(chan_id=peer_channel.chan_id, peer_alias=peer_channel.alias, setting='Enabled', old_value=1, new_value=0).save()
|
||||
print('Auto Pilot Disabled (3): ', peer_channel.alias, ' : ', peer_channel.chan_id, ' Out: ', oapD, ' In: ', iapD )
|
||||
elif oapD < (iapD*1.10) and inbound_percent > 75:
|
||||
#print('Case 4: Pass')
|
||||
pass
|
||||
else:
|
||||
#print('Case 5: Pass')
|
||||
pass
|
||||
|
||||
def main():
|
||||
|
||||
rebalances = Rebalancer.objects.filter(status=0).order_by('id')
|
||||
if len(rebalances) == 0:
|
||||
auto_enable()
|
||||
auto_schedule()
|
||||
else:
|
||||
rebalance = rebalances[0]
|
||||
#print('Next Rebalance for:', rebalance.target_alias, ' : ', rebalance.last_hop_pubkey, ' Amount:', rebalance.value, ' Duration:', rebalance.duration )
|
||||
while rebalance != None:
|
||||
rebalance = run_rebalancer(rebalance)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -4,4 +4,5 @@ django-qr-code
|
|||
grpcio
|
||||
protobuf
|
||||
pytz
|
||||
pandas
|
||||
pandas
|
||||
requests
|
||||
Loading…
Add table
Add a link
Reference in a new issue