diff --git a/gui/af.py b/gui/af.py
index 676a0071..22aa2086 100644
--- a/gui/af.py
+++ b/gui/af.py
@@ -5,53 +5,26 @@
from pandas import DataFrame, concat
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
-from gui.models import Forwards, Channels, LocalSettings, FailedHTLCs
+from gui.models import Forwards, Channels, Groups, Settings, FailedHTLCs
-def main(channels):
- channels_df = DataFrame.from_records(channels.values())
+def main(group: Groups, chan_id=''):
+ if len(chan_id) > 0:
+ channels_df = DataFrame.from_records(group.channels.filter(chan_id=chan_id).values() if group.channels.filter(chan_id=chan_id).exists() else Channels.objects.filter(chan_id=chan_id).values())
+ else:
+ channels_df = DataFrame.from_records(group.channels.filter(is_open=True, private=False).values())
+
filter_1day = datetime.now() - timedelta(days=1)
filter_7day = datetime.now() - timedelta(days=7)
if channels_df.shape[0] > 0:
- if LocalSettings.objects.filter(key='AF-MaxRate').exists():
- max_rate = int(LocalSettings.objects.filter(key='AF-MaxRate')[0].value)
- else:
- LocalSettings(key='AF-MaxRate', value='2500').save()
- max_rate = 2500
- if LocalSettings.objects.filter(key='AF-MinRate').exists():
- min_rate = int(LocalSettings.objects.filter(key='AF-MinRate')[0].value)
- else:
- LocalSettings(key='AF-MinRate', value='0').save()
- min_rate = 0
- if LocalSettings.objects.filter(key='AF-Increment').exists():
- increment = int(LocalSettings.objects.filter(key='AF-Increment')[0].value)
- else:
- LocalSettings(key='AF-Increment', value='5').save()
- increment = 5
- if LocalSettings.objects.filter(key='AF-Multiplier').exists():
- multiplier = int(LocalSettings.objects.filter(key='AF-Multiplier')[0].value)
- else:
- LocalSettings(key='AF-Multiplier', value='5').save()
- multiplier = 5
- if LocalSettings.objects.filter(key='AF-FailedHTLCs').exists():
- failed_htlc_limit = int(LocalSettings.objects.filter(key='AF-FailedHTLCs')[0].value)
- else:
- LocalSettings(key='AF-FailedHTLCs', value='25').save()
- failed_htlc_limit = 25
- if LocalSettings.objects.filter(key='AF-UpdateHours').exists():
- update_hours = int(LocalSettings.objects.filter(key='AF-UpdateHours').get().value)
- else:
- LocalSettings(key='AF-UpdateHours', value='24').save()
- update_hours = 24
- if LocalSettings.objects.filter(key='AF-LowLiqLimit').exists():
- lowliq_limit = int(LocalSettings.objects.filter(key='AF-LowLiqLimit').get().value)
- else:
- LocalSettings(key='AF-LowLiqLimit', value='5').save()
- lowliq_limit = 5
- if LocalSettings.objects.filter(key='AF-ExcessLimit').exists():
- excess_limit = int(LocalSettings.objects.filter(key='AF-ExcessLimit').get().value)
- else:
- LocalSettings(key='AF-ExcessLimit', value='95').save()
- excess_limit = 95
+ max_rate = int(group.settings_set.filter(key='AF-MaxRate')[0].value if group.settings_set.filter(key='AF-MaxRate').exists() else Settings.objects.filter(group_id=0,key='AF-MaxRate')[0].value)
+ min_rate = int(group.settings_set.filter(key='AF-MinRate')[0].value if group.settings_set.filter(key='AF-MinRate').exists() else Settings.objects.filter(group_id=0,key='AF-MinRate')[0].value)
+ increment = int(group.settings_set.filter(key='AF-Increment')[0].value if group.settings_set.filter(key='AF-Increment').exists() else Settings.objects.filter(group_id=0,key='AF-Increment')[0].value)
+ multiplier = int(group.settings_set.filter(key='AF-Multiplier')[0].value if group.settings_set.filter(key='AF-Multiplier').exists() else Settings.objects.filter(group_id=0,key='AF-Multiplier')[0].value)
+ failed_htlc_limit = int(group.settings_set.filter(key='AF-FailedHTLCs')[0].value if group.settings_set.filter(key='AF-FailedHTLCs').exists() else Settings.objects.filter(group_id=0,key='AF-FailedHTLCs')[0].value)
+ update_hours = int(group.settings_set.filter(key='AF-UpdateHours').get().value if group.settings_set.filter(key='AF-UpdateHours').exists() else Settings.objects.filter(group_id=0,key='AF-UpdateHours')[0].value)
+ lowliq_limit = int(group.settings_set.filter(key='AF-LowLiqLimit').get().value if group.settings_set.filter(key='AF-LowLiqLimit').exists() else Settings.objects.filter(group_id=0,key='AF-LowLiqLimit')[0].value)
+ excess_limit = int(group.settings_set.filter(key='AF-ExcessLimit').get().value if group.settings_set.filter(key='AF-ExcessLimit').exists() else Settings.objects.filter(group_id=0,key='AF-ExcessLimit')[0].value)
+
if lowliq_limit >= excess_limit:
print('Invalid thresholds detected, using defaults...')
lowliq_limit = 5
@@ -115,4 +88,6 @@ def main(channels):
if __name__ == '__main__':
- print(main(Channels.objects.filter(is_open=True)))
\ No newline at end of file
+ for g in Groups.objects.prefetch_related('channels').all():
+ print(f"{datetime.now().strftime('%c')} : [AF] : Running on group: {g.name}")
+ main(g)
\ No newline at end of file
diff --git a/gui/forms.py b/gui/forms.py
index 9e9d3c98..87f61ad6 100644
--- a/gui/forms.py
+++ b/gui/forms.py
@@ -63,39 +63,41 @@ class Meta:
last_hop_pubkey = forms.CharField(label='funding_txid', max_length=66, required=False)
duration = forms.IntegerField(label='duration')
-class AutoRebalanceForm(forms.Form):
- enabled = forms.IntegerField(label='enabled', required=False)
- target_percent = forms.FloatField(label='target_percent', required=False)
- 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)
- workers = forms.IntegerField(label='workers', required=False)
- update_channels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
+class AutoRebalanceForm(forms.Form):
+ group_id = forms.IntegerField(label="group_id", required=True)
+ group_name = forms.CharField(label="group_name", min_length=1, max_length=20, required=False)
+ ar_enabled = forms.IntegerField(label='ar_enabled', required=False)
+ ar_target_percent = forms.FloatField(label='ar_target_percent', required=False)
+ ar_time = forms.IntegerField(label='ar_time', required=False)
+ ar_maxfeerate = forms.IntegerField(label='ar_maxfeerate', required=False)
+ ar_outbound_percent = forms.FloatField(label='ar_outbound_percent', required=False)
+ ar_inbound_percent = forms.FloatField(label='ar_inbound_percent', required=False)
+ ar_maxcost_percent = forms.FloatField(label='ar_maxcost_percent', required=False)
+ ar_variance = forms.IntegerField(label='ar_variance', required=False)
+ ar_waitperiod = forms.IntegerField(label='ar_waitperiod', required=False)
+ ar_autopilot = forms.IntegerField(label='ar_autopilot', required=False)
+ ar_apdays = forms.IntegerField(label='ar_apdays', required=False)
+ ar_workers = forms.IntegerField(label='ar_workers', required=False)
+ ar_update_channels = forms.BooleanField(widget=forms.CheckboxSelectMultiple, required=False)
class AutoFeesForm(AutoRebalanceForm):
af_enabled = forms.IntegerField(label='af_enabled', required=False)
- af_maxRate = forms.IntegerField(label='af_maxRate', required=False)
- af_minRate = forms.IntegerField(label='af_minRate', required=False)
+ af_maxrate = forms.IntegerField(label='af_maxrate', required=False)
+ af_minrate = forms.IntegerField(label='af_minrate', required=False)
af_increment = forms.IntegerField(label='af_increment', required=False)
af_multiplier = forms.IntegerField(label='af_multiplier', required=False)
- af_failedHTLCs = forms.IntegerField(label='af_failedHTLCs', required=False)
- af_updateHours = forms.IntegerField(label='af_updateHours', required=False)
- af_lowliq = forms.IntegerField(label='af_lowliq', required=False)
- af_excess = forms.IntegerField(label='af_excess', required=False)
+ af_failedhtlcs = forms.IntegerField(label='af_failedhtlcs', required=False)
+ af_updatehours = forms.IntegerField(label='af_updatehours', required=False)
+ af_lowliqlimit = forms.IntegerField(label='af_lowliqlimit', required=False)
+ af_excesslimit = forms.IntegerField(label='af_excesslimit', required=False)
class GUIForm(AutoFeesForm):
- gui_graphLinks = forms.CharField(label='gui_graphLinks', required=False)
- gui_netLinks = forms.CharField(label='gui_netLinks', required=False)
+ gui_graphlinks = forms.CharField(label='gui_graphlinks', required=False)
+ gui_netlinks = forms.CharField(label='gui_netlinks', required=False)
-class LocalSettingsForm(GUIForm):
- lnd_cleanPayments = forms.IntegerField(label='lnd_cleanPayments', required=False)
- lnd_retentionDays = forms.IntegerField(label='lnd_retentionDays', required=False)
+class SettingsForm(GUIForm):
+ lnd_cleanpayments = forms.IntegerField(label='lnd_cleanpayments', required=False)
+ lnd_retentiondays = forms.IntegerField(label='lnd_retentiondays', required=False)
updates_channel_codes = [
(0, 'base_fee'),
diff --git a/gui/migrations/0037_groups_settings_delete_localsettings_channels_groups.py b/gui/migrations/0037_groups_settings_delete_localsettings_channels_groups.py
new file mode 100644
index 00000000..0c1006b2
--- /dev/null
+++ b/gui/migrations/0037_groups_settings_delete_localsettings_channels_groups.py
@@ -0,0 +1,77 @@
+# Generated by Django 4.1.5 on 2023-08-06 17:59
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+from gui.models import Groups, Channels, Settings
+
+def migrate_update_channels(apps, schema_editor):
+ channels = Channels.objects.all()
+ lndg_default = Groups(name="", id=0) # default
+ lndg_default.save()
+ for ch in channels:
+ lndg_default.channels.add(ch)
+ lndg_default.save()
+
+ print('\nInitializing default settings...')
+ Settings(key='AF-MaxRate', value='2500',group=lndg_default).save()
+ Settings(key='AF-MinRate', value='0',group=lndg_default).save()
+ Settings(key='AF-Increment', value='5',group=lndg_default).save()
+ Settings(key='AF-Multiplier', value='5',group=lndg_default).save()
+ Settings(key='AF-FailedHTLCs', value='25',group=lndg_default).save()
+ Settings(key='AF-UpdateHours', value='24',group=lndg_default).save()
+ Settings(key='AF-LowLiqLimit', value='5',group=lndg_default).save()
+ Settings(key='AF-ExcessLimit', value='95',group=lndg_default).save()
+ Settings(key='AF-Enabled', value='0',group=lndg_default).save()
+ Settings(key='GUI-GraphLinks', value='https://amboss.space',group=lndg_default).save()
+ Settings(key='GUI-NetLinks', value='https://mempool.space',group=lndg_default).save()
+ Settings(key='LND-CleanPayments', value='0',group=lndg_default).save()
+ Settings(key='LND-RetentionDays', value='30',group=lndg_default).save()
+ Settings(key='AR-Outbound%', value='75',group=lndg_default).save()
+ Settings(key='AR-Inbound%', value='100',group=lndg_default).save()
+ Settings(key='AR-Target%', value='5',group=lndg_default).save()
+ Settings(key='AR-MaxCost%', value='65',group=lndg_default).save()
+ Settings(key='AR-Enabled', value='0',group=lndg_default).save()
+ Settings(key='AR-MaxFeeRate', value='100',group=lndg_default).save()
+ Settings(key='AR-Variance', value='0',group=lndg_default).save()
+ Settings(key='AR-WaitPeriod', value='30',group=lndg_default).save()
+ Settings(key='AR-Time', value='5',group=lndg_default).save()
+ Settings(key='AR-Autopilot', value='0',group=lndg_default).save()
+ Settings(key='AR-APDays', value='7',group=lndg_default).save()
+ Settings(key='AR-Workers', value='1',group=lndg_default).save()
+
+ print('\nApplying custom settings if user has any...')
+ for sett in apps.get_model('gui', 'localsettings').objects.all():
+ setting = lndg_default.settings_set.get(key=sett.key)
+ setting.value = sett.value
+ setting.save()
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('gui', '0036_peers'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Groups',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=20, unique=True)),
+ ('channels', models.ManyToManyField(related_name='groups_set', to='gui.channels')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Settings',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('key', models.CharField(default=None, max_length=20)),
+ ('value', models.CharField(default=None, max_length=50)),
+ ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='gui.groups')),
+ ],
+ ),
+ migrations.RunPython(migrate_update_channels, migrations.RunPython.noop),
+ migrations.DeleteModel(
+ name='LocalSettings',
+ )
+ ]
diff --git a/gui/models.py b/gui/models.py
index 8d8ced6f..61d7f8d2 100644
--- a/gui/models.py
+++ b/gui/models.py
@@ -110,43 +110,12 @@ class Channels(models.Model):
auto_fees = models.BooleanField()
notes = models.TextField(default='', blank=True)
- 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)
- else:
- 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)
- else:
- LocalSettings(key='AR-Target%', value='5').save()
- amt_setting = 5
- self.ar_amt_target = int((amt_setting/100) * self.capacity)
- if not self.ar_max_cost:
- if LocalSettings.objects.filter(key='AR-MaxCost%').exists():
- cost_setting = int(LocalSettings.objects.filter(key='AR-MaxCost%')[0].value)
- else:
- LocalSettings(key='AR-MaxCost%', value='65').save()
- cost_setting = 65
- self.ar_max_cost = cost_setting
- super(Channels, self).save(*args, **kwargs)
+ class Meta:
+ app_label = 'gui'
+
+class Groups(models.Model):
+ name = models.CharField(max_length=20, unique=True)
+ channels = models.ManyToManyField(Channels, related_name="groups_set",)
class Meta:
app_label = 'gui'
@@ -181,9 +150,10 @@ class Rebalancer(models.Model):
class Meta:
app_label = 'gui'
-class LocalSettings(models.Model):
- key = models.CharField(primary_key=True, default=None, max_length=20)
+class Settings(models.Model):
+ key = models.CharField(max_length=20,default=None)
value = models.CharField(default=None, max_length=50)
+ group = models.ForeignKey(Groups, on_delete=models.CASCADE)
class Meta:
app_label = 'gui'
diff --git a/gui/serializers.py b/gui/serializers.py
index 4aeb4a18..21cc28e5 100644
--- a/gui/serializers.py
+++ b/gui/serializers.py
@@ -1,6 +1,6 @@
from rest_framework import serializers
from rest_framework.relations import PrimaryKeyRelatedField
-from .models import LocalSettings, Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, Peers, Onchain, PendingHTLCs, FailedHTLCs, Closures, Resolutions, PeerEvents, Autofees
+from .models import Settings, Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, Peers, Onchain, PendingHTLCs, FailedHTLCs, Closures, Resolutions, PeerEvents, Groups, Autofees
##FUTURE UPDATE 'exclude' TO 'fields'
@@ -88,6 +88,14 @@ class Meta:
def get_opened_in(self, obj):
return int(obj.short_chan_id.split('x')[0])
+class GroupsSerializer(serializers.HyperlinkedModelSerializer):
+ id = serializers.ReadOnlyField()
+ name = serializers.ReadOnlyField(required=False)
+ channels = serializers.PrimaryKeyRelatedField(many=True,queryset=Channels.objects.all(),default=[])
+ class Meta:
+ model = Groups
+ exclude = []
+
class RebalancerSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.ReadOnlyField()
requested = serializers.ReadOnlyField()
@@ -172,10 +180,10 @@ class Meta:
model = PaymentHops
exclude = []
-class LocalSettingsSerializer(serializers.HyperlinkedModelSerializer):
+class SettingsSerializer(serializers.HyperlinkedModelSerializer):
key = serializers.ReadOnlyField()
class Meta:
- model = LocalSettings
+ model = Settings
exclude = []
class PendingHTLCSerializer(serializers.HyperlinkedModelSerializer):
diff --git a/gui/static/api.js b/gui/static/api.js
index afdad3ca..15341b86 100644
--- a/gui/static/api.js
+++ b/gui/static/api.js
@@ -21,9 +21,10 @@ async function DELETE(url, {method = 'DELETE'} = {}){
}
async function call({url, method, data, body, headers = {'Content-Type':'application/json'}}){
- if(url.charAt(url.length-1) != '/') url += '/'
+ if(!url.endsWith('/')) url += '/'
if(method != 'GET') headers['X-CSRFToken'] = document.getElementById('api').dataset.token
- const result = await fetch(`api/${url}${data ? '?': ''}${new URLSearchParams(data).toString()}`, {method, body: JSON.stringify(body), headers})
+ const result = await fetch(`${window.location.origin}/api/${url}${data ? '?': ''}${new URLSearchParams(data).toString()}`, {method, body: JSON.stringify(body), headers})
+ if (result.status == 204) return
return result.json()
}
@@ -34,4 +35,7 @@ class Sync{
static POST(url, {method = 'POST', body}, callback){
call({url, method, body}).then(res => callback(res))
}
+ static DELETE(url, {method = 'DELETE', body}, callback){
+ call({url, method, body}).then(res => callback(res))
+ }
}
\ No newline at end of file
diff --git a/gui/static/helpers.js b/gui/static/helpers.js
index 7e7d5d30..8f24d34c 100644
--- a/gui/static/helpers.js
+++ b/gui/static/helpers.js
@@ -46,15 +46,15 @@ async function toggle(button){
}
function use(template){
return {
- render: function(object, id='id', row = null){
+ render: function(object, id='id', columnType = "td", row = undefined){
const tr = row ?? document.createElement("tr")
tr.objId = object[id]
for (key in template){
- const transforms = template[key](object)
- const td = document.createElement("td")
- td.setAttribute('name', key)
- td.render(transforms)
- tr.append(td)
+ const transforms = template[key](object, key)
+ const col = document.createElement(columnType)
+ col.setAttribute('name', key)
+ col.render(transforms)
+ tr.append(col)
}
return tr
}
diff --git a/gui/static/w3style.css b/gui/static/w3style.css
index 6a98ba3b..c38c6cd7 100644
--- a/gui/static/w3style.css
+++ b/gui/static/w3style.css
@@ -260,6 +260,8 @@ input:not([type=submit]):not([type=checkbox]):focus, select:focus {outline: none
.input {position:relative;float:left;padding:0px 4px}.input::after{position:absolute;right: 25px;top:2px;opacity:.8;content:attr(data-unit);color:#555599}
.encrypted td:not(th){-webkit-text-security:circle;}.encrypted thead th:not(a){-webkit-text-security:circle;}
.soft-encrypted table td, .soft-encrypted table thead{color: transparent;text-shadow: 0 0 5px rgba(0,0,0,0.1);}
+.btn {cursor: pointer;} .close:hover {color:red;}
+.dark-mode .w3-text-black{color:white!important}
.switch{position:relative;display:inline-block;width:45px;height:25.5px}
.switch input {opacity:0;width:0;height:0}
.slider {position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#ccc;-webkit-transition:.4s;transition:.4s}
diff --git a/gui/templates/advanced.html b/gui/templates/advanced.html
index 8ec53ee4..8bfb407f 100644
--- a/gui/templates/advanced.html
+++ b/gui/templates/advanced.html
@@ -209,13 +209,10 @@
Advanced Channel Settings
-{% endif %}
-{% if not channels %}
+{% else %}
You dont have any channels to setup yet!
{% endif %}
-{% if local_settings %}
-{% include 'local_settings.html' with settings=local_settings title='Update Local' %}
-{% endif %}
+{% include 'settings.html' with configs="'AR-,AF-,GUI-,LND-'" title='Update' %}
{% endblock %}
diff --git a/gui/templates/base.html b/gui/templates/base.html
index f7925d4a..a7ca4e81 100644
--- a/gui/templates/base.html
+++ b/gui/templates/base.html
@@ -8,14 +8,19 @@
@@ -98,7 +114,7 @@
{% for message in messages %}
X
-
{{ message.message }}
+ {{ message.message |safe }}
{% with sliced_msg=message.message|slice:":17" %}
{% if sliced_msg == "Deposit Address: " or sliced_msg == "Invoice created! " %}
{% qr_from_text message.message|slice:"17:" size="s" image_format="png" error_correction="L" %}
diff --git a/gui/templates/fee_rates.html b/gui/templates/fee_rates.html
index c549b3cd..65c28614 100644
--- a/gui/templates/fee_rates.html
+++ b/gui/templates/fee_rates.html
@@ -84,7 +84,5 @@ Suggested Fee Rates
You dont have any channels to analyze yet!
{% endif %}
-{% if local_settings %}
-{% include 'local_settings.html' with settings=local_settings title='Auto-Fees' postURL='update_setting' %}
-{% endif %}
+{% include 'settings.html' with configs="'AF-'" title='Auto-Fees' postURL='update_setting' %}
{% endblock %}
\ No newline at end of file
diff --git a/gui/templates/home.html b/gui/templates/home.html
index 0ca38ca1..3970d2cb 100644
--- a/gui/templates/home.html
+++ b/gui/templates/home.html
@@ -162,8 +162,7 @@
- Channel ID
- Peer Alias
+ Peer Alias
Outbound Liquidity
Capacity
Inbound Liquidity
@@ -188,8 +187,8 @@
- Channel ID
- Peer Alias
+ Peer Alias
+ Channel ID
Outbound Liquidity
Capacity
Inbound Liquidity
@@ -410,7 +409,7 @@
-{% include 'local_settings.html' with settings=local_settings title="Auto-Rebalancer" url='rebalancing' %}
+{% include 'settings.html' with configs="'AR-'" title="Auto-Rebalancer" url='rebalancing' %}
Connect to a Peer
@@ -954,8 +953,21 @@
Sign a Message
const invoices_task = GET('invoices', {data: {state__lt: 2, limit: 10} })
const failedHTLCs_task = GET('failedhtlcs', {data: {wire_failure__lt: 99, limit:10} })
+ const groups = (await GET('groups', {data: {} })).results
let [updates, pending_htlcs, active, inactive, private] = [0,0,[],[],[]]
- for (ch of (await GET('channels', {data: {is_open:true} })).results){ //basic setup
+ for (ch of (await GET('channels', {data: {is_open: true} })).results){
+ ch.tags = []
+ ch.groups = []
+ for (group of groups){
+ if(group.id > 0 && !group.channels.includes(ch.chan_id)) { //assignable groups
+ ch.groups.push(group)
+ continue
+ }
+ channels = group.channels.filter(c => c !== ch.chan_id)
+ if (group.id === 0) continue
+ ch.tags.push({id:group.id, name:group.name, channels:channels})
+ }
+
updates += ch.num_updates
pending_htlcs += ch.htlc_count
ch.local_balance += ch.pending_outbound
diff --git a/gui/templates/local_settings.html b/gui/templates/local_settings.html
deleted file mode 100644
index 1bad6556..00000000
--- a/gui/templates/local_settings.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
\ No newline at end of file
diff --git a/gui/templates/rebalancing.html b/gui/templates/rebalancing.html
index 5102668e..58f5c497 100644
--- a/gui/templates/rebalancing.html
+++ b/gui/templates/rebalancing.html
@@ -9,20 +9,19 @@
PK
- Channel ID
- Peer Alias
+ Peer
Outbound Liquidity
Capacity
Inbound Liquidity
Rebalance
- Fee Ratio
+ Fee Ratio
Target Amt
Max Cost
Out Target
In Target
AR
7-Day Rate
- Active
+ Active
@@ -153,9 +152,21 @@ Manual Rebalance R
return
}
+ const groups = (await GET('groups', {data: {} })).results
let [enabled, elegible, available, rebal_table] = [0,0,0,byId("rebalancingTable")]
resp.results.forEach(ch => {
extend(ch)
+ ch.tags = []
+ ch.groups = []
+ for (group of groups){
+ if(group.id > 0 && !group.channels.includes(ch.chan_id)) { //assignable groups
+ ch.groups.push(group)
+ continue
+ }
+ channels = group.channels.filter(c => c !== ch.chan_id)
+ if (group.id === 0) continue
+ ch.tags.push({id:group.id, name:group.name, channels:channels})
+ }
elegible += ch.elegible
available += ch.available
enabled += ch.auto_rebalance
@@ -182,5 +193,5 @@ Manual Rebalance R
}
{% include 'rebalances_table.html' with count=20 load_count=10 title='Rebalance Requests ' %}
-{% include 'local_settings.html' with settings=local_settings title='Auto-Rebalancer' %}
+{% include 'settings.html' with configs="'AR-'" title='Auto-Rebalancer' %}
{% endblock %}
diff --git a/gui/templates/settings.html b/gui/templates/settings.html
new file mode 100644
index 00000000..15ddaeae
--- /dev/null
+++ b/gui/templates/settings.html
@@ -0,0 +1,80 @@
+
+
{% if url %}{{title}} {% else %}{{title}}{% endif %} Settings
+
+
+
+
\ No newline at end of file
diff --git a/gui/urls.py b/gui/urls.py
index 55ab7f9d..77890292 100644
--- a/gui/urls.py
+++ b/gui/urls.py
@@ -14,10 +14,11 @@
router.register(r'peers', views.PeersViewSet)
router.register(r'channels', views.ChannelsViewSet)
router.register(r'rebalancer', views.RebalancerViewSet)
-router.register(r'settings', views.LocalSettingsViewSet)
+router.register(r'settings', views.SettingsViewSet)
router.register(r'pendinghtlcs', views.PendingHTLCViewSet)
router.register(r'failedhtlcs', views.FailedHTLCViewSet)
router.register(r'peerevents', views.PeerEventsViewSet)
+router.register(r'groups', views.GroupsViewSet)
router.register(r'feelog', views.FeeLogViewSet)
urlpatterns = [
@@ -69,6 +70,7 @@
path('advanced/', views.advanced, name='advanced'),
path('logs/', views.logs, name='logs'),
path('addresses/', views.addresses, name='addresses'),
+ path('new_group/', views.new_group, name='new-group'),
path('api/', include(router.urls), name='api-root'),
path('api-auth/', include('rest_framework.urls'), name='api-auth'),
path('api/connectpeer/', views.connect_peer, name='connect-peer'),
@@ -89,5 +91,6 @@
path('api/node_info/', views.node_info, name='node-info'),
path('api/forwards_summary/', views.forwards_summary, name='forwards-summary'),
path('api/sign_message/', views.sign_message, name='sign-message'),
+ path('api/get_settings/', views.get_settings, name='get-group-settings'),
path('lndg-admin/', admin.site.urls),
]
diff --git a/gui/views.py b/gui/views.py
index 355213c8..4d3ee1c8 100644
--- a/gui/views.py
+++ b/gui/views.py
@@ -9,9 +9,9 @@
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
-from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, UpdateChannel, UpdateSetting, LocalSettingsForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending, UpdateClosing, UpdateKeysend, AddAvoid, RemoveAvoid
-from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, LocalSettings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels, AvoidNodes, PeerEvents
-from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, LocalSettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer, BumpFeeSerializer, UpdateChanPolicy, NewAddressSerializer, BroadcastTXSerializer, PeerEventsSerializer, SignMessageSerializer, FeeLogSerializer
+from .forms import OpenChannelForm, CloseChannelForm, ConnectPeerForm, AddInvoiceForm, RebalancerForm, UpdateChannel, UpdateSetting, SettingsForm, AddTowerForm, RemoveTowerForm, DeleteTowerForm, BatchOpenForm, UpdatePending, UpdateClosing, UpdateKeysend, AddAvoid, RemoveAvoid
+from .models import Payments, PaymentHops, Invoices, Forwards, Channels, Rebalancer, Settings, Peers, Onchain, Closures, Resolutions, PendingHTLCs, FailedHTLCs, Autopilot, Autofees, PendingChannels, AvoidNodes, PeerEvents, Groups
+from .serializers import ConnectPeerSerializer, FailedHTLCSerializer, SettingsSerializer, OpenChannelSerializer, CloseChannelSerializer, AddInvoiceSerializer, PaymentHopsSerializer, PaymentSerializer, InvoiceSerializer, ForwardSerializer, ChannelSerializer, PendingHTLCSerializer, RebalancerSerializer, UpdateAliasSerializer, PeerSerializer, OnchainSerializer, ClosuresSerializer, ResolutionsSerializer, BumpFeeSerializer, UpdateChanPolicy, NewAddressSerializer, BroadcastTXSerializer, PeerEventsSerializer, SignMessageSerializer, GroupsSerializer, FeeLogSerializer
from gui.lnd_deps import lightning_pb2 as ln
from gui.lnd_deps import lightning_pb2_grpc as lnrpc
from gui.lnd_deps import router_pb2 as lnr
@@ -23,24 +23,16 @@
from gui.lnd_deps.lnd_connect import lnd_connect
from lndg import settings
from os import path
-from pandas import DataFrame, merge
+from pandas import DataFrame, merge, concat
from requests import get
from . import af
def graph_links():
- if LocalSettings.objects.filter(key='GUI-GraphLinks').exists():
- graph_links = str(LocalSettings.objects.filter(key='GUI-GraphLinks')[0].value)
- else:
- LocalSettings(key='GUI-GraphLinks', value='https://amboss.space').save()
- graph_links = 'https://amboss.space'
+ graph_links = str(Settings.objects.filter(key='GUI-GraphLinks',group_id=0)[0].value)
return graph_links
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'
+ network_links = str(Settings.objects.filter(key='GUI-NetLinks',group_id=0)[0].value)
return network_links
def get_tx_fees(txid):
@@ -83,7 +75,6 @@ def home(request):
return render(request, 'error.html', {'error': error})
return render(request, 'home.html', {
'node_info': {'color': node_info.color, 'alias': node_info.alias, 'version': node_info.version, 'identity_pubkey': node_info.identity_pubkey, 'uris': node_info.uris},
- 'local_settings': get_local_settings('AR-'),
'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links(),
@@ -187,11 +178,10 @@ def channels(request):
@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def fees(request):
if request.method == 'GET':
- channels = Channels.objects.filter(is_open=True, private=False)
- results_df = af.main(channels)
+ results_df = concat([af.main(g) for g in Groups.objects.prefetch_related('channels').all()])
+ results_df = results_df.groupby(['remote_pubkey','chan_id', 'short_chan_id']).agg(lambda x: (x.values.sum()/x.size).astype(int) if x.dtype in ['int64','float64'] else x.values.any()).reset_index()
context = {
'channels': [] if results_df.empty else results_df.sort_values(by=['out_percent']).to_dict(orient='records'),
- 'local_settings': get_local_settings('AF-'),
'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
@@ -215,7 +205,6 @@ def advanced(request):
channels_df['local_max_htlc'] = channels_df['local_max_htlc_msat']/1000
context = {
'channels': channels_df.to_dict(orient='records'),
- 'local_settings': get_local_settings('AF-', 'AR-', 'GUI-', 'LND-'),
'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links(),
'network_links': network_links()
@@ -705,6 +694,7 @@ def channel(request):
else:
node_outbound = channels_df['local_balance'].sum()
node_capacity = channels_df['capacity'].sum()
+ group = Groups.objects.filter(channels=chan_id,settings__key__contains="AF")[0] if Groups.objects.filter(channels=chan_id,settings__key__contains="AF").exists() else Groups.objects.filter(id=0)[0]
channel = Channels.objects.filter(chan_id=chan_id)
channels_df = DataFrame.from_records(channel.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())
@@ -1001,7 +991,7 @@ def channel(request):
autofees_df = DataFrame.from_records(Autofees.objects.filter(chan_id=chan_id).filter(timestamp__gte=filter_30day).order_by('-id').values())
if autofees_df.shape[0]> 0:
autofees_df['change'] = autofees_df.apply(lambda row: 0 if row.old_value == 0 else round((row.new_value-row.old_value)*100/row.old_value, 1), axis=1)
- results_df = af.main(channel)
+ results_df = af.main(group, chan_id)
channels_df['new_rate'] = results_df[results_df['chan_id']==chan_id]['new_rate']
channels_df['adjustment'] = results_df[results_df['chan_id']==chan_id]['adjustment']
channels_df['net_routed_7day'] = results_df[results_df['chan_id']==chan_id]['net_routed_7day']
@@ -1363,7 +1353,6 @@ def forwards(request):
def rebalancing(request):
if request.method == 'GET':
context = {
- 'local_settings': get_local_settings('AR-'),
'network': 'testnet/' if settings.LND_NETWORK == 'testnet' else '',
'graph_links': graph_links()
}
@@ -1608,112 +1597,146 @@ def rebalance(request):
messages.error(request, 'Invalid Request. Please try again.')
return redirect(request.META.get('HTTP_REFERER'))
-def get_local_settings(*prefixes):
- form = []
- if 'AR-' in prefixes:
- form.append({'unit': '', 'form_id': 'update_channels', 'id': 'update_channels'})
- form.append({'unit': '', 'form_id': 'enabled', 'value': 0, 'label': 'AR Enabled', 'id': 'AR-Enabled', 'title':'This enables or disables the auto-scheduling function', 'min':0, 'max':1},)
- form.append({'unit': '%', 'form_id': 'target_percent', 'value': 5, 'label': 'AR Target Amount', 'id': 'AR-Target%', 'title': 'The percentage of the total capacity to target as the rebalance amount', 'min':0.1, 'max':100})
- form.append({'unit': 'min', 'form_id': 'target_time', 'value': 5, 'label': 'AR Target Time', 'id': 'AR-Time', 'title': 'The time spent per individual rebalance attempt', 'min':1, 'max':60})
- form.append({'unit': 'ppm', 'form_id': 'fee_rate', 'value': 100, 'label': 'AR Max Fee Rate', 'id': 'AR-MaxFeeRate', 'title': 'The max rate we can ever use to refill a channel with outbound', 'min':1, 'max':5000})
- form.append({'unit': '%', 'form_id': 'outbound_percent', 'value': 75, 'label': 'AR Target Out Above', 'id': 'AR-Outbound%', 'title': 'When a channel is not enabled for targeting; the minimum outbound a channel must have to be a source for refilling another channel', 'min':1, 'max':100})
- form.append({'unit': '%', 'form_id': 'inbound_percent', 'value': 100, 'label': 'AR Target In Above', 'id': 'AR-Inbound%', 'title': 'When a channel is enabled for targeting; the maximum inbound a channel can have before selected for auto rebalance', 'min':1, 'max':100})
- form.append({'unit': '%', 'form_id': 'max_cost', 'value': 65, 'label': 'AR Max Cost', 'id': 'AR-MaxCost%', 'title': 'The ppm to target which is the percentage of the outbound fee rate for the channel being refilled', 'min':1, 'max':100})
- form.append({'unit': '%', 'form_id': 'variance', 'value': 0, 'label': 'AR Variance', 'id': 'AR-Variance', 'title': 'The percentage of the target amount to be randomly varied with every rebalance attempt', 'min':0, 'max':100})
- form.append({'unit': 'min', 'form_id': 'wait_period', 'value': 30, 'label': 'AR Wait Period', 'id': 'AR-WaitPeriod', 'title': 'The minutes we should wait after a failed attempt before trying again', 'min':1, 'max':10080})
- form.append({'unit': '', 'form_id': 'autopilot', 'value': 0, 'label': 'Autopilot', 'id': 'AR-Autopilot', 'title': 'This enables or disables the Autopilot function which automatically acts upon suggestions on this page: /actions', 'min':0, 'max':1})
- form.append({'unit': 'days', 'form_id': 'autopilotdays', 'value': 7, 'label': 'Autopilot Days', 'id': 'AR-APDays', 'title': 'Number of days to consider for autopilot. Default 7', 'min':0, 'max':100})
- form.append({'unit': '', 'form_id': 'workers', 'value': 1, 'label': 'Workers', 'id': 'AR-Workers', 'title': 'Number of workers', 'min':1, 'max':12})
- if 'AF-' in prefixes:
- form.append({'unit': '', 'form_id': 'af_enabled', 'value': 0, 'label': 'Autofee', 'id': 'AF-Enabled', 'title': 'Enable/Disable Auto-fee functionality', 'min':0, 'max':1})
- form.append({'unit': 'ppm', 'form_id': 'af_maxRate', 'value': 2500, 'label': 'AF Max Rate', 'id': 'AF-MaxRate', 'title': 'Maximum Rate', 'min':0, 'max':5000})
- form.append({'unit': 'ppm', 'form_id': 'af_minRate', 'value': 0, 'label': 'AF Min Rate', 'id': 'AF-MinRate', 'title': 'Minimum Rate', 'min':0, 'max':5000})
- form.append({'unit': 'ppm', 'form_id': 'af_increment', 'value': 5, 'label': 'AF Increment', 'id': 'AF-Increment', 'title': 'Amount to increment on each interaction', 'min':1, 'max':100})
- form.append({'unit': 'x', 'form_id': 'af_multiplier', 'value': 5, 'label': 'AF Multiplier', 'id': 'AF-Multiplier', 'title': 'Multiplier to be applied to Auto-Fee', 'min':1, 'max':100})
- form.append({'unit': '', 'form_id': 'af_failedHTLCs', 'value': 25, 'label': 'AF FailedHTLCs', 'id': 'AF-FailedHTLCs', 'title': 'Failed HTLCs', 'min':1, 'max':100})
- form.append({'unit': 'hours', 'form_id': 'af_updateHours', 'value': 24, 'label': 'AF Update', 'id': 'AF-UpdateHours', 'title': 'Number of hours to consider to update fees. Default 24', 'min':1, 'max':100})
- form.append({'unit': '%', 'form_id': 'af_lowliq', 'value': 15, 'label': 'AF LowLiq', 'id': 'AF-LowLiqLimit', 'title': 'Limit for low liq AF rules. Default 5', 'min':0, 'max':100})
- form.append({'unit': '%', 'form_id': 'af_excess', 'value': 90, 'label': 'AF Excess', 'id': 'AF-ExcessLimit', 'title': 'Limit for excess liq AF rules. Default 95', 'min':0, 'max':100})
- if 'GUI-' in prefixes:
- form.append({'unit': '', 'form_id': 'gui_graphLinks', 'value': 'https://amboss.space', 'label': 'Graph URL', 'id': 'GUI-GraphLinks', 'title': 'Preferred Graph URL'})
- form.append({'unit': '', 'form_id': 'gui_netLinks', 'value': 'https://mempool.space', 'label': 'NET URL', 'id': 'GUI-NetLinks', 'title': 'Preferred NET URL'})
- if 'LND-' in prefixes:
- form.append({'unit': '', 'form_id': 'lnd_cleanPayments', 'value': 0, 'label': 'LND Clean Payments', 'id': 'LND-CleanPayments', 'title': 'Clean LND Payments', 'min':0, 'max':1})
- form.append({'unit': 'days', 'form_id': 'lnd_retentionDays', 'value': 30, 'label': 'LND Retention', 'id': 'LND-RetentionDays', 'title': 'LND Retention days', 'min':1, 'max':1000})
-
- for prefix in prefixes:
- ar_settings = LocalSettings.objects.filter(key__contains=prefix).values('key', 'value').order_by('key')
- for field in form:
- for sett in ar_settings:
- if field['id'] == sett['key']:
- field['value'] = sett['value']
- break
- return form
+@api_view(['GET'])
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def get_settings(request):
+ groups = Groups.objects.all()
+ response = []
+ for group in groups:
+ settings = group.settings_set.values("key", "value").order_by('key').all()
+ settings = {i['key']: i['value'] for i in settings}
+ fields = [{'unit': '', 'form_id': 'group_name', 'group_id': group.id, 'name': 'group_name', 'id': 'group_name', 'value': group.name, 'label': 'Group Name', 'title': 'Group Name' }]
+ fields.append({'group_id': group.id, 'unit': '', 'form_id': 'ar_enabled', 'value': settings.get('AR-Enabled'), 'label': 'Enabled', 'id': 'AR-Enabled', 'title':'This enables or disables the auto-scheduling function', 'min':0, 'max':1})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'ar_target_percent', 'value': settings.get('AR-Target%'), 'label': 'Target Amount', 'id': 'AR-Target%', 'title': 'The percentage of the total capacity to target as the rebalance amount', 'min':0.1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': 'min', 'form_id': 'ar_time', 'value': settings.get('AR-Time'), 'label': 'Target Time', 'id': 'AR-Time', 'title': 'The time spent per individual rebalance attempt', 'min':1, 'max':60})
+ fields.append({'group_id': group.id, 'unit': 'ppm', 'form_id': 'ar_maxfeerate', 'value': settings.get('AR-MaxFeeRate'), 'label': 'Max Fee Rate', 'id': 'AR-MaxFeeRate', 'title': 'The max rate we can ever use to refill a channel with outbound', 'min':1, 'max':5000})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'ar_outbound_percent', 'value': settings.get('AR-Outbound%'), 'label': 'Target Out Above', 'id': 'AR-Outbound%', 'title': 'When a channel is not enabled for targeting; the minimum outbound a channel must have to be a source for refilling another channel', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'ar_inbound_percent', 'value': settings.get('AR-Inbound%'), 'label': 'Target In Above', 'id': 'AR-Inbound%', 'title': 'When a channel is enabled for targeting; the maximum inbound a channel can have before selected for auto rebalance', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'ar_maxcost_percent', 'value': settings.get('AR-MaxCost%'), 'label': 'Max Cost', 'id': 'AR-MaxCost%', 'title': 'The ppm to target which is the percentage of the outbound fee rate for the channel being refilled', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'ar_variance', 'value': settings.get('AR-Variance'), 'label': 'Variance', 'id': 'AR-Variance', 'title': 'The percentage of the target amount to be randomly varied with every rebalance attempt', 'min':0, 'max':100})
+ fields.append({'group_id': group.id, 'unit': 'min', 'form_id': 'ar_waitperiod', 'value': settings.get('AR-WaitPeriod'), 'label': 'Wait Period', 'id': 'AR-WaitPeriod', 'title': 'The minutes we should wait after a failed attempt before trying again', 'min':1, 'max':10080})
+ fields.append({'group_id': group.id, 'unit': '', 'form_id': 'ar_autopilot', 'value': settings.get('AR-Autopilot'), 'label': 'Autopilot', 'id': 'AR-Autopilot', 'title': 'This enables or disables the Autopilot function which automatically acts upon suggestions on this page: /actions', 'min':0, 'max':1})
+ fields.append({'group_id': group.id, 'unit': 'days', 'form_id': 'ar_apdays', 'value': settings.get('AR-APDays'), 'label': 'Autopilot Days', 'id': 'AR-APDays', 'title': 'Number of days to consider for autopilot. Default 7', 'min':0, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '', 'form_id': 'ar_update_channels', 'value': 0, 'label': 'Update Channels', 'id': 'AR-update_channels', 'title': 'Also updates current (AR-Target%, AR-Outbound% , AR-Inbound%, AR-MaxCost%) for all channels in this group.'})
+ fields.append({'group_id': 0, 'unit': '', 'form_id': 'ar_workers', 'value': settings.get('AR-Workers'), 'label': 'Workers', 'id': 'AR-Workers', 'title': 'Number of workers', 'min':1, 'max':12})
+ fields.append({'group_id': group.id, 'unit': '', 'form_id': 'af_enabled', 'value': settings.get('AF-Enabled'), 'label': 'Autofee', 'id': 'AF-Enabled', 'title': 'Enable/Disable Auto-fee functionality', 'min':0, 'max':1})
+ fields.append({'group_id': group.id, 'unit': 'ppm', 'form_id': 'af_maxrate', 'value': settings.get('AF-MaxRate'), 'label': 'Max Rate', 'id': 'AF-MaxRate', 'title': 'Maximum Rate', 'min':0, 'max':5000})
+ fields.append({'group_id': group.id, 'unit': 'ppm', 'form_id': 'af_minrate', 'value': settings.get('AF-MinRate'), 'label': 'Min Rate', 'id': 'AF-MinRate', 'title': 'Minimum Rate', 'min':0, 'max':5000})
+ fields.append({'group_id': group.id, 'unit': 'ppm', 'form_id': 'af_increment', 'value': settings.get('AF-Increment'), 'label': 'Increment', 'id': 'AF-Increment', 'title': 'Amount to increment on each interaction', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': 'x', 'form_id': 'af_multiplier', 'value': settings.get('AF-Multiplier'), 'label': 'Multiplier', 'id': 'AF-Multiplier', 'title': 'Multiplier to be applied to Auto-Fee', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '', 'form_id': 'af_failedhtlcs', 'value': settings.get('AF-FailedHTLCs'), 'label': 'FailedHTLCs', 'id': 'AF-FailedHTLCs', 'title': 'Failed HTLCs', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': 'hours', 'form_id': 'af_updatehours', 'value': settings.get('AF-UpdateHours'), 'label': 'Update', 'id': 'AF-UpdateHours', 'title': 'Number of hours to consider to update fees. Default 24', 'min':1, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'af_lowliqlimit', 'value': settings.get('AF-LowLiqLimit'), 'label': 'LowLiq', 'id': 'AF-LowLiqLimit', 'title': 'Limit for low liq AF rules. Default 5', 'min':0, 'max':100})
+ fields.append({'group_id': group.id, 'unit': '%', 'form_id': 'af_excesslimit', 'value': settings.get('AF-ExcessLimit'), 'label': 'Excess', 'id': 'AF-ExcessLimit', 'title': 'Limit for excess liq AF rules. Default 95', 'min':0, 'max':100})
+ fields.append({'group_id': 0, 'unit': '', 'form_id': 'gui_graphlinks', 'value': settings.get('GUI-GraphLinks'), 'label': 'Graph URL', 'id': 'GUI-GraphLinks', 'title': 'Preferred Graph URL'})
+ fields.append({'group_id': 0, 'unit': '', 'form_id': 'gui_netlinks', 'value': settings.get('GUI-NetLinks'), 'label': 'NET URL', 'id': 'GUI-NetLinks', 'title': 'Preferred NET URL'})
+ fields.append({'group_id': 0, 'unit': '', 'form_id': 'lnd_cleanpayments', 'value': settings.get('LND-CleanPayments'), 'label': 'Clean Payments', 'id': 'LND-CleanPayments', 'title': 'Clean LND Payments', 'min':0, 'max':1})
+ fields.append({'group_id': 0, 'unit': 'days', 'form_id': 'lnd_retentiondays', 'value': settings.get('LND-RetentionDays'), 'label': 'Retention', 'id': 'LND-RetentionDays', 'title': 'LND Retention days', 'min':1, 'max':1000})
+
+ response.append(fields)
+ return Response({'groups': response})
+
+@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
+def new_group(request):
+ if request.method == 'POST':
+ try:
+ next = Groups.objects.order_by('-id')[0].id + 1
+ name = request.POST.get('name', "group_"+ str(next))
+ Groups(name=name).save()
+ except Exception as e:
+ messages.error(request, "ERROR: "+str(e))
+ return redirect(request.META.get('HTTP_REFERER'))
+
@is_login_required(login_required(login_url='/lndg-admin/login/?next=/'), settings.LOGIN_REQUIRED)
def update_settings(request):
if request.method == 'POST':
- template = [{'form_id': 'enabled', 'value': 0, 'parse': lambda x: x,'id': 'AR-Enabled'},
- {'form_id': 'target_percent', 'value': 5, 'parse': lambda x: float(x),'id': 'AR-Target%'},
- {'form_id': 'target_time', 'value': 5, 'parse': lambda x: x,'id': 'AR-Time'},
- {'form_id': 'fee_rate', 'value': 100, 'parse': lambda x: x,'id': 'AR-MaxFeeRate'},
- {'form_id': 'outbound_percent', 'value': 75, 'parse': lambda x: int(x),'id': 'AR-Outbound%'},
- {'form_id': 'inbound_percent', 'value': 100, 'parse': lambda x: int(x),'id': 'AR-Inbound%'},
- {'form_id': 'max_cost', 'value': 65, 'parse': lambda x: int(x),'id': 'AR-MaxCost%'},
- {'form_id': 'variance', 'value': 0, 'parse': lambda x: x,'id': 'AR-Variance'},
- {'form_id': 'wait_period', 'value': 30, 'parse': lambda x: x,'id': 'AR-WaitPeriod'},
- {'form_id': 'autopilot', 'value': 0, 'parse': lambda x: x,'id': 'AR-Autopilot'},
- {'form_id': 'autopilotdays', 'value': 7, 'parse': lambda x: x,'id': 'AR-APDays'},
- {'form_id': 'workers', 'value': 1, 'parse': lambda x: x,'id': 'AR-Workers'},
+ template = [{'form_id': 'ar_enabled', 'value': 0, 'id': 'AR-Enabled'},
+ {'form_id': 'ar_target_percent', 'value': 5, 'id': 'AR-Target%'},
+ {'form_id': 'ar_time', 'value': 5, 'id': 'AR-Time'},
+ {'form_id': 'ar_maxfeerate', 'value': 100, 'id': 'AR-MaxFeeRate'},
+ {'form_id': 'ar_outbound_percent', 'value': 75, 'id': 'AR-Outbound%'},
+ {'form_id': 'ar_inbound_percent', 'value': 100, 'id': 'AR-Inbound%'},
+ {'form_id': 'ar_maxcost_percent', 'value': 65, 'id': 'AR-MaxCost%'},
+ {'form_id': 'ar_variance', 'value': 0, 'id': 'AR-Variance'},
+ {'form_id': 'ar_waitperiod', 'value': 30, 'id': 'AR-WaitPeriod'},
+ {'form_id': 'ar_autopilot', 'value': 0, 'id': 'AR-Autopilot'},
+ {'form_id': 'ar_apdays', 'value': 7, 'id': 'AR-APDays'},
#AF
- {'form_id': 'af_enabled', 'value': 0, 'parse': lambda x: int(x),'id': 'AF-Enabled'},
- {'form_id': 'af_maxRate', 'value': 2500, 'parse': lambda x: int(x),'id': 'AF-MaxRate'},
- {'form_id': 'af_minRate', 'value': 0, 'parse': lambda x: int(x),'id': 'AF-MinRate'},
- {'form_id': 'af_increment', 'value': 5, 'parse': lambda x: int(x),'id': 'AF-Increment'},
- {'form_id': 'af_multiplier', 'value': 5, 'parse': lambda x: int(x),'id': 'AF-Multiplier'},
- {'form_id': 'af_failedHTLCs', 'value': 25, 'parse': lambda x: int(x),'id': 'AF-FailedHTLCs'},
- {'form_id': 'af_updateHours', 'value': 24, 'parse': lambda x: int(x),'id': 'AF-UpdateHours'},
- {'form_id': 'af_lowliq', 'value': 15, 'parse': lambda x: int(x),'id': 'AF-LowLiqLimit'},
- {'form_id': 'af_excess', 'value': 90, 'parse': lambda x: int(x),'id': 'AF-ExcessLimit'},
- #GUI
- {'form_id': 'gui_graphLinks', 'value': 'https://amboss.space', 'parse': lambda x: x,'id': 'GUI-GraphLinks'},
- {'form_id': 'gui_netLinks', 'value': 'https://mempool.space', 'parse': lambda x: x,'id': 'GUI-NetLinks'},
- #LND
- {'form_id': 'lnd_cleanPayments', 'value': 0, 'parse': lambda x: x, 'id': 'LND-CleanPayments'},
- {'form_id': 'lnd_retentionDays', 'value': 30, 'parse': lambda x: x, 'id': 'LND-RetentionDays'},
- ]
-
- form = LocalSettingsForm(request.POST)
+ {'form_id': 'af_enabled', 'value': 0, 'id': 'AF-Enabled'},
+ {'form_id': 'af_maxrate', 'value': 2500, 'id': 'AF-MaxRate'},
+ {'form_id': 'af_minrate', 'value': 0, 'id': 'AF-MinRate'},
+ {'form_id': 'af_increment', 'value': 5, 'id': 'AF-Increment'},
+ {'form_id': 'af_multiplier', 'value': 5, 'id': 'AF-Multiplier'},
+ {'form_id': 'af_failedhtlcs', 'value': 25, 'id': 'AF-FailedHTLCs'},
+ {'form_id': 'af_updatehours', 'value': 24, 'id': 'AF-UpdateHours'},
+ {'form_id': 'af_lowliqlimit', 'value': 15, 'id': 'AF-LowLiqLimit'},
+ {'form_id': 'af_excesslimit', 'value': 90, 'id': 'AF-ExcessLimit'}]
+
+ form = SettingsForm(request.POST)
if not form.is_valid():
messages.error(request, 'Invalid Request. Please try again.')
- else:
- update_channels = form.cleaned_data['update_channels']
- for field in template:
- value = form.cleaned_data[field['form_id']]
- if value is not None:
- value = field['parse'](value)
- try:
- db_value = LocalSettings.objects.get(key=field['id'])
- except:
- LocalSettings(key=field['id'], value=field['value']).save()
- db_value = LocalSettings.objects.get(key=field['id'])
- if db_value.value == str(value) or len(str(value)) == 0:
- continue
- db_value.value = value
- db_value.save()
-
- if update_channels and field['id'] in ['AR-Target%', 'AR-Outbound%','AR-Inbound%','AR-MaxCost%']:
- if field['id'] == 'AR-Target%':
- Channels.objects.all().update(ar_amt_target=Round(F('capacity')*(value/100), output_field=IntegerField()))
- elif field['id'] == 'AR-Outbound%':
- Channels.objects.all().update(ar_out_target=value)
- elif field['id'] == 'AR-Inbound%':
- Channels.objects.all().update(ar_in_target=value)
- elif field['id'] == 'AR-MaxCost%':
- Channels.objects.all().update(ar_max_cost=value)
- messages.success(request, 'All channels ' + field['id'] + ' updated to: ' + str(value))
- else:
- messages.success(request, field['id'] + ' updated to: ' + str(value))
+ return redirect(request.META.get('HTTP_REFERER'))
+
+ group_id = form.cleaned_data['group_id']
+ update_channels = form.cleaned_data['ar_update_channels']
+
+ if group_id == 0:
+ template.extend([{'form_id': 'ar_workers', 'value': 1, 'id': 'AR-Workers'},
+ {'form_id': 'gui_graphlinks', 'value': 'https://amboss.space', 'id': 'GUI-GraphLinks'},
+ {'form_id': 'gui_netlinks', 'value': 'https://mempool.space', 'id': 'GUI-NetLinks'},
+ {'form_id': 'lnd_cleanpayments', 'value': 0, 'id': 'LND-CleanPayments'},
+ {'form_id': 'lnd_retentiondays', 'value': 30, 'id': 'LND-RetentionDays'}])
+
+ group = Groups.objects.prefetch_related('settings_set').prefetch_related('channels').get(id=group_id)
+ changing = [f['id'] if not f['id'].endswith('Enabled') else None for f in template if form.cleaned_data[f['form_id']] or None]
+ if len(changing) > 0:
+ for ch in group.channels.all():
+ for g in ch.groups_set.exclude(id__in=[0,group.id]).all():
+ dup = list(g.settings_set.filter(key__in=changing).values_list('key'))
+ dup_setts = ",".join([""+s[0]+" " for s in dup])
+ if len(dup) > 0:
+ messages.error(request, "Conflicting value(s) between "+group.name+" and "+g.name+" ("+dup_setts+") ")
+ return redirect(request.META.get('HTTP_REFERER'))
+
+ if group.name != form.cleaned_data['group_name']:
+ group.name = form.cleaned_data['group_name']
+ group.save()
+
+ for field in template:
+ value = form.cleaned_data[field['form_id']]
+ value = float(value) if value is not None and field['form_id'] == 'ar_target_percent' else value
+ try:
+ db_value = group.settings_set.get(key=field['id'])
+ except Exception:
+ db_value = Settings(key=field['id'], group_id=group_id)
+ if db_value.value == str(value) or len(str(value)) == 0:
+ continue
+ if group.id == 0 and value is None and db_value.value is not None:
+ continue
+ if value is None and db_value.value is not None:
+ old_value = db_value.value
+ db_value.delete()
+ elif value is not None:
+ old_value = db_value.value
+ db_value.value = value
+ db_value.save()
+ else:
+ continue
+
+ if update_channels and field['id'] in ['AR-Target%', 'AR-Outbound%','AR-Inbound%','AR-MaxCost%']:
+ if field['id'] == 'AR-Target%':
+ group.channels.update(ar_amt_target=Round(F('capacity')*(value/100), output_field=IntegerField()))
+ elif field['id'] == 'AR-Outbound%':
+ group.channels.update(ar_out_target=value)
+ elif field['id'] == 'AR-Inbound%':
+ group.channels.update(ar_in_target=value)
+ elif field['id'] == 'AR-MaxCost%':
+ group.channels.update(ar_max_cost=value)
+ messages.success(request, ''+ (group.name or 'LNDg') +' ::' + field['id'] + ' changed from '+ str(old_value) +' to ' + str(value) + ' for all channels in this group ')
+ else:
+ text = ''+ (group.name or 'LNDg') +' ::' + field['id'] + ' changed from '+ str(old_value) +' to ' + str(value) + ' '
+ messages.success(request, text)
return redirect(request.META.get('HTTP_REFERER'))
@@ -1846,7 +1869,7 @@ def update_pending(request):
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:
- auto_fees = int(LocalSettings.objects.filter(key='AF-Enabled')[0].value) if LocalSettings.objects.filter(key='AF-Enabled').exists() else 0
+ auto_fees = int(Groups.objects.filter(channels=pending_channel.id,settings__key__contains="AF")[0].settings_set.get('AF-Enabled').value)
pending_channel.auto_fees = True if pending_channel.auto_fees == False or (pending_channel.auto_fees == None and auto_fees == 0) 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))
@@ -1941,13 +1964,7 @@ def update_setting(request):
target = int(value)
channels = Channels.objects.filter(is_open=True, private=False).update(auto_fees=target)
messages.success(request, 'Auto Fees setting for all channels updated to a value of: ' + str(target))
- try:
- db_enabled = LocalSettings.objects.get(key='AF-UpdateHours')
- except:
- LocalSettings(key='AF-UpdateHours', value='24').save()
- db_enabled = LocalSettings.objects.get(key='AF-UpdateHours')
- db_enabled.value = target
- db_enabled.save()
+ Settings.objects.filter(group_id=0, key='AF-UpdateHours').update(value=target)
messages.success(request, 'Updated autofees update hours setting to: ' + str(target))
else:
messages.error(request, 'Invalid Request. Please try again. [' + key +']')
@@ -2162,20 +2179,68 @@ class FailedHTLCViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = FailedHTLCSerializer
filterset_class = FailedHTLCFilter
-class LocalSettingsViewSet(viewsets.ReadOnlyModelViewSet):
+class SettingsViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
- queryset = LocalSettings.objects.all()
- serializer_class = LocalSettingsSerializer
+ queryset = Settings.objects.all()
+ serializer_class = SettingsSerializer
def update(self, request, pk):
setting = get_object_or_404(self.queryset, pk=pk)
- serializer = LocalSettingsSerializer(setting, data=request.data, context={'request': request})
+ serializer = SettingsSerializer(setting, data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
else:
return Response(serializer.errors)
+class GroupsViewSet(viewsets.ReadOnlyModelViewSet):
+ permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
+ queryset = Groups.objects.all()
+ serializer_class = GroupsSerializer
+
+ def update(self, request, pk):
+ if int(pk) == 0: return Response(status=403) #Forbidden
+ group = get_object_or_404(self.queryset, pk=pk)
+ serializer = self.get_serializer(group, data=request.data, context={'request': request}, partial=True)
+ if serializer.is_valid():
+ lndg = Groups.objects.prefetch_related('channels').get(id=0)
+ adding = lndg.channels.filter(chan_id__in=request.data.get('channels'))
+ [lndg.channels.remove(ch) for ch in adding]
+ lndg.save()
+
+ adding = Channels.objects.prefetch_related('groups_set').exclude(chan_id__in=group.channels.all()).filter(chan_id__in=request.data.get('channels')).all()
+ for ch in adding:
+ current_settings = group.settings_set.values_list('key', flat=True).all()
+ dup_settings = ch.groups_set.filter(settings__key__in=current_settings).values_list('settings__key', flat=True).all()
+ if len(dup_settings) > 0:
+ return Response(status=403, data={'status':403, 'message':"Cannot add "+ch.alias+" to "+group.name+" - duplicate settings: "+ ",".join(dup_settings) +" "})
+
+ removing = group.channels.exclude(chan_id__in=request.data.get('channels')).all()
+ for ch in removing:
+ if ch.groups_set.count() == 1:
+ lndg.channels.add(ch)
+
+ lndg.save()
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors)
+
+ def delete(self, request, pk):
+ if int(pk) == 0: return Response(status=403) #Forbidden
+ group = get_object_or_404(self.queryset, pk=pk)
+ try:
+ mappings = Channels.objects.filter(chan_id__in=group.channels.all()).annotate(count=Count('groups_set')).filter(count=1).values('count','chan_id').all()
+ if len(mappings) > 0:
+ lndg = Groups.objects.get(id=0)
+ [lndg.channels.add(ch['chan_id']) for ch in mappings]
+ lndg.save()
+ group.delete()
+ except Exception as e:
+ print('Channels might have already been on LNDg group:' + str(e))
+
+ return Response(status=204) #no content
+
+
class ChannelsViewSet(viewsets.ReadOnlyModelViewSet):
permission_classes = [IsAuthenticated] if settings.LOGIN_REQUIRED else []
queryset = Channels.objects.all()
@@ -2277,11 +2342,11 @@ def node_info(request):
peers = Peers.objects.all()
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
+ inbound_setting = int(Settings.objects.filter(group_id=0, key='AR-Inbound%')[0].value)
+ outbound_setting = int(Settings.objects.filter(group_id=0, key='AR-Outbound%')[0].value)
+ amt_setting = float(Settings.objects.filter(group_id=0, key='AR-Target%')[0].value)
+ cost_setting = int(Settings.objects.filter(group_id=0, key='AR-MaxCost%')[0].value)
+ auto_fees = int(Settings.objects.filter(group_id=0, key='AF-Enabled')[0].value)
for i in range(0,len(target_resp)):
item = {}
pending_open_balance += target_resp[i].channel.local_balance
diff --git a/jobs.py b/jobs.py
index 090e5fd4..90b5c80e 100644
--- a/jobs.py
+++ b/jobs.py
@@ -13,7 +13,7 @@
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, PendingChannels, HistFailedHTLC, PeerEvents
+from gui.models import Groups, Payments, PaymentHops, Invoices, Forwards, Channels, Peers, Onchain, Closures, Resolutions, PendingHTLCs, Settings, FailedHTLCs, Autofees, PendingChannels, HistFailedHTLC, PeerEvents
from gui import af
def update_payments(stub):
@@ -164,6 +164,7 @@ def update_channels(stub):
chan_list = []
channels = stub.ListChannels(ln.ListChannelsRequest()).channels
PendingHTLCs.objects.all().delete()
+ LNDg = Groups.objects.get(id=0)
for channel in channels:
if Channels.objects.filter(chan_id=channel.chan_id).exists():
#Update the channel record with the most current data
@@ -179,7 +180,6 @@ def update_channels(stub):
txid, index = channel_point.split(':')
db_channel = Channels()
db_channel.remote_pubkey = channel.remote_pubkey
- db_channel.chan_id = channel.chan_id
db_channel.short_chan_id = str(channel.chan_id >> 40) + 'x' + str(channel.chan_id >> 16 & 0xFFFFFF) + 'x' + str(channel.chan_id & 0xFFFF)
db_channel.initiator = channel.initiator
db_channel.alias = alias
@@ -189,6 +189,12 @@ def update_channels(stub):
db_channel.private = channel.private
db_channel.push_amt = channel.push_amount_sat
db_channel.close_address = channel.close_address
+ db_channel.auto_fees = 0 #init default settings
+ db_channel.ar_out_target = 75
+ db_channel.ar_in_target = 100
+ db_channel.ar_amt_target = 5
+ db_channel.is_active = False
+ db_channel.ar_max_cost = 65 #end default settings
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
# Update basic channel data
db_channel.local_balance = channel.local_balance
@@ -337,7 +343,13 @@ def update_channels(stub):
#External Fee change detected, update auto fee log
db_channel.fees_updated = datetime.now()
Autofees(chan_id=db_channel.chan_id, peer_alias=db_channel.alias, setting=(f"Ext"), old_value=old_fee_rate, new_value=db_channel.local_fee_rate).save()
- db_channel.save()
+ if not db_channel.chan_id:
+ db_channel.chan_id = channel.chan_id
+ db_channel.save()
+ LNDg.channels.add(db_channel)
+ LNDg.save()
+ else:
+ db_channel.save()
counter += 1
chan_list.append(channel.chan_id)
records = Channels.objects.filter(is_open=True).count()
@@ -394,11 +406,7 @@ def update_onchain(stub):
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'
+ network_links = str(Settings.objects.filter(key='GUI-NetLinks', group_id=0)[0].value)
return network_links
def get_tx_fees(txid):
@@ -481,18 +489,10 @@ def reconnect_peers(stub):
peer.save()
def clean_payments(stub):
- if LocalSettings.objects.filter(key='LND-CleanPayments').exists():
- enabled = int(LocalSettings.objects.filter(key='LND-CleanPayments')[0].value)
- else:
- LocalSettings(key='LND-CleanPayments', value='0').save()
- LocalSettings(key='LND-RetentionDays', value='30').save()
- enabled = 0
+ enabled = int(Settings.objects.filter(key='LND-CleanPayments', group_id=0)[0].value)
+
if enabled == 1:
- if LocalSettings.objects.filter(key='LND-RetentionDays').exists():
- retention_days = int(LocalSettings.objects.filter(key='LND-RetentionDays')[0].value)
- else:
- LocalSettings(key='LND-RetentionDays', value='30').save()
- retention_days = 30
+ retention_days = int(Settings.objects.filter(key='LND-RetentionDays', group_id=0)[0].value)
time_filter = datetime.now() - timedelta(days=retention_days)
target_payments = Payments.objects.exclude(status=1).filter(cleaned=False).filter(creation_date__lte=time_filter).order_by('index')[:10]
for payment in target_payments:
@@ -511,32 +511,26 @@ def clean_payments(stub):
payment.save()
def auto_fees(stub):
- if LocalSettings.objects.filter(key='AF-Enabled').exists():
- if int(LocalSettings.objects.filter(key='AF-Enabled')[0].value) == 0: #disabled
- return
- else:
- LocalSettings(key='AF-Enabled', value='0').save()
- return
-
try:
- channels = Channels.objects.filter(is_open=True, is_active=True, private=False, auto_fees=True)
- results_df = af.main(channels)
- if not results_df.empty:
- update_df = results_df[results_df['eligible'] == True]
- update_df = update_df[update_df['adjustment']!=0]
- if not update_df.empty:
- for target_channel in update_df.to_dict(orient='records'):
- print(f"{datetime.now().strftime('%c')} : [Data] : Updating fees for channel {str(target_channel['chan_id'])} to a value of: {str(target_channel['new_rate'])}")
- channel = Channels.objects.filter(chan_id=target_channel['chan_id'])[0]
- channel_point = ln.ChannelPoint()
- channel_point.funding_txid_bytes = bytes.fromhex(channel.funding_txid)
- channel_point.funding_txid_str = channel.funding_txid
- channel_point.output_index = channel.output_index
- stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=channel.local_base_fee, fee_rate=(target_channel['new_rate']/1000000), time_lock_delta=channel.local_cltv))
- channel.local_fee_rate = target_channel['new_rate']
- channel.fees_updated = datetime.now()
- channel.save()
- Autofees(chan_id=channel.chan_id, peer_alias=channel.alias, setting=(f"AF [ {target_channel['net_routed_7day']}:{target_channel['in_percent']}:{target_channel['out_percent']} ]"), old_value=target_channel['local_fee_rate'], new_value=target_channel['new_rate']).save()
+ groups = Groups.objects.filter(settings__key='AF-Enabled', settings__value='1').all()
+ for group in groups:
+ results_df = af.main(group)
+ if not results_df.empty:
+ update_df = results_df[results_df['eligible'] == True]
+ update_df = update_df[update_df['adjustment']!=0]
+ if not update_df.empty:
+ for target_channel in update_df.to_dict(orient='records'):
+ print(f"{datetime.now().strftime('%c')} : [Data] : Updating fees for channel {str(target_channel['chan_id'])} to a value of: {str(target_channel['new_rate'])}")
+ channel = Channels.objects.filter(chan_id=target_channel['chan_id'])[0]
+ channel_point = ln.ChannelPoint()
+ channel_point.funding_txid_bytes = bytes.fromhex(channel.funding_txid)
+ channel_point.funding_txid_str = channel.funding_txid
+ channel_point.output_index = channel.output_index
+ stub.UpdateChannelPolicy(ln.PolicyUpdateRequest(chan_point=channel_point, base_fee_msat=channel.local_base_fee, fee_rate=(target_channel['new_rate']/1000000), time_lock_delta=channel.local_cltv))
+ channel.local_fee_rate = target_channel['new_rate']
+ channel.fees_updated = datetime.now()
+ channel.save()
+ Autofees(chan_id=channel.chan_id, peer_alias=channel.alias, setting=(f"AF [ {target_channel['net_routed_7day']}:{target_channel['in_percent']}:{target_channel['out_percent']} ]"), old_value=target_channel['local_fee_rate'], new_value=target_channel['new_rate']).save()
except Exception as e:
print(f"{datetime.now().strftime('%c')} : [Data] : Error processing auto_fees: {str(e)}")
diff --git a/rebalancer.py b/rebalancer.py
index ef728c84..637ba1d6 100644
--- a/rebalancer.py
+++ b/rebalancer.py
@@ -13,7 +13,7 @@
environ['DJANGO_SETTINGS_MODULE'] = 'lndg.settings'
django.setup()
-from gui.models import Rebalancer, Channels, LocalSettings, Forwards, Autopilot
+from gui.models import Groups, Rebalancer, Channels, Settings, Forwards, Autopilot
@sync_to_async
def get_out_cans(rebalance, auto_rebalance_channels):
@@ -179,53 +179,34 @@ def update_channels(stub, incoming_channel, outgoing_channel):
except Exception as e:
print(f"{datetime.now().strftime('%c')} : [Rebalancer] : Error updating channel balances: {str(e)}")
+def use(set):
+ def get(key):
+ return next(filter(lambda x: x.key == key, list(set)), Settings.objects.filter(group_id=0,key=key)[0]).value
+ return get
+
@sync_to_async
def auto_schedule() -> List[Rebalancer]:
try:
#No rebalancer jobs have been scheduled, lets look for any channels with an auto_rebalance flag and make the best request if we find one
to_schedule = []
- if LocalSettings.objects.filter(key='AR-Enabled').exists():
- enabled = int(LocalSettings.objects.filter(key='AR-Enabled')[0].value)
- else:
- LocalSettings(key='AR-Enabled', value='0').save()
- enabled = 0
- if enabled == 0:
- return []
-
- 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'))
- if len(auto_rebalance_channels) == 0:
- return []
+ 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(channels.filter(auto_rebalance=False, percent_outbound__gte=F('ar_out_target')).values_list('chan_id', flat=True))
- 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))
already_scheduled = Rebalancer.objects.exclude(last_hop_pubkey='').filter(status=0).values_list('last_hop_pubkey')
- inbound_cans = auto_rebalance_channels.filter(auto_rebalance=True, inbound_can__gte=1).exclude(remote_pubkey__in=already_scheduled).order_by('-inbound_can')
+ inbound_cans = channels.filter(auto_rebalance=True, inbound_can__gte=1).exclude(remote_pubkey__in=already_scheduled).order_by('-inbound_can')
if len(inbound_cans) == 0 or len(outbound_cans) == 0:
- return []
+ return
- if LocalSettings.objects.filter(key='AR-MaxFeeRate').exists():
- max_fee_rate = int(LocalSettings.objects.filter(key='AR-MaxFeeRate')[0].value)
- else:
- LocalSettings(key='AR-MaxFeeRate', value='100').save()
- max_fee_rate = 100
- if LocalSettings.objects.filter(key='AR-Variance').exists():
- variance = int(LocalSettings.objects.filter(key='AR-Variance')[0].value)
- else:
- LocalSettings(key='AR-Variance', value='0').save()
- variance = 0
- if LocalSettings.objects.filter(key='AR-WaitPeriod').exists():
- wait_period = int(LocalSettings.objects.filter(key='AR-WaitPeriod')[0].value)
- else:
- LocalSettings(key='AR-WaitPeriod', value='30').save()
- wait_period = 30
- if not LocalSettings.objects.filter(key='AR-Target%').exists():
- LocalSettings(key='AR-Target%', value='5').save()
- if not LocalSettings.objects.filter(key='AR-MaxCost%').exists():
- LocalSettings(key='AR-MaxCost%', value='65').save()
for target in inbound_cans:
+ setts = use(Settings.objects.raw('''select * from gui_groups_channels c
+ join gui_groups g on c.groups_id = g.id
+ join gui_settings s on g.id = s.group_id
+ where c.channels_id = %s''', [target.chan_id]))
+
+ max_fee_rate = int(setts('AR-MaxFeeRate'))
+ variance = int(setts('AR-Variance'))
+ wait_period = int(setts('AR-WaitPeriod'))
+
target_fee_rate = int(target.local_fee_rate * (target.ar_max_cost/100))
if target_fee_rate > 0 and target_fee_rate > target.remote_fee_rate:
target_value = int(target.ar_amt_target+(target.ar_amt_target*((secrets.choice(range(-1000,1001))/1000)*variance/100)))
@@ -233,11 +214,7 @@ def auto_schedule() -> List[Rebalancer]:
if target_fee == 0:
continue
- if LocalSettings.objects.filter(key='AR-Time').exists():
- target_time = int(LocalSettings.objects.filter(key='AR-Time')[0].value)
- else:
- LocalSettings(key='AR-Time', value='5').save()
- target_time = 5
+ target_time = int(setts('AR-Time'))
# TLDR: willing to pay 1 sat for every value_per_fee sats moved
if Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).exists():
last_rebalance = Rebalancer.objects.filter(last_hop_pubkey=target.remote_pubkey).exclude(status=0).order_by('-id')[0]
@@ -259,18 +236,10 @@ def auto_schedule() -> List[Rebalancer]:
@sync_to_async
def auto_enable():
try:
- if LocalSettings.objects.filter(key='AR-Autopilot').exists():
- enabled = int(LocalSettings.objects.filter(key='AR-Autopilot')[0].value)
- 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:
- lookup_channels=Channels.objects.filter(is_active=True, is_open=True, private=False)
+ groups = Groups.objects.filter(settings__key='AR-Autopilot',settings__value='1').all()
+ for group in groups:
+ apdays = int(group.settings_set.filter(key='AR-APDays')[0].value if group.settings_set.filter(key='AR-APDays').exists() else Settings.objects.filter(group_id=0, key='AR-APDays')[0].value)
+ lookup_channels= group.channels.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)
@@ -283,7 +252,7 @@ def auto_enable():
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)
+ #print('Processing: ', peer_channel.alias, ' : ', peer_channel.chan_id, ' : ', oapD, " : ", iapD, ' : ', outbound_percent, ' : ', inbound_percent)
if peer_channel.ar_out_target == 100 and peer_channel.auto_rebalance == True:
#Special Case for LOOP, Wos, etc. Always Auto Rebalance if enabled to keep outbound full.
print(f"{datetime.now().strftime('%c')} : [Rebalancer] : Skipping AR enabled and 100% oTarget channel... {peer_channel.alias=} {peer_channel.chan_id=}")
@@ -385,8 +354,8 @@ async def start_queue(worker_count=1):
@sync_to_async
def get_worker_count():
- if LocalSettings.objects.filter(key='AR-Workers').exists():
- return int(LocalSettings.objects.filter(key='AR-Workers')[0].value)
+ if Settings.objects.filter(key='AR-Workers').exists():
+ return int(Settings.objects.filter(key='AR-Workers')[0].value)
else:
return 1
@@ -402,10 +371,10 @@ async def update_worker_count():
def main():
global scheduled_rebalances, active_rebalances, shutdown_rebalancer, worker_count
- if LocalSettings.objects.filter(key='AR-Workers').exists():
- worker_count = int(LocalSettings.objects.filter(key='AR-Workers')[0].value)
+ if Settings.objects.filter(key='AR-Workers').exists():
+ worker_count = int(Settings.objects.filter(key='AR-Workers')[0].value)
else:
- LocalSettings(key='AR-Workers', value='1').save()
+ Settings(key='AR-Workers', value='1').save()
worker_count = 1
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
@@ -420,7 +389,9 @@ def main():
unknown_error.status = 400
unknown_error.stop = datetime.now()
unknown_error.save()
+ worker_count = int(Settings.objects.filter(key='AR-Workers', group_id=0)[0].value)
loop.run_until_complete(start_queue(worker_count))
+
print(f"{datetime.now().strftime('%c')} : [Rebalancer] : Rebalancer successfully exited...sleeping for 20 seconds")
sleep(20)