Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add tags to channels #314

Draft
wants to merge 41 commits into
base: v1.9.0
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
6df0542
Add models & migration
proof-of-reality Aug 2, 2023
fb77bad
Make `key` + `group` unique together
proof-of-reality Aug 4, 2023
c1d47d0
Add/cp initial values from localsettings to settings
proof-of-reality Aug 4, 2023
cfddb47
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Aug 4, 2023
88dd372
Remove LocalSettings and else conditions
proof-of-reality Aug 4, 2023
d17c813
Add groups to channels
proof-of-reality Aug 4, 2023
4fa10c2
Fix errors and switch tasks to be groups oriented
proof-of-reality Aug 4, 2023
dec1b15
Fix issue with positional arg
proof-of-reality Aug 4, 2023
37fa83f
Add groups to settings form
proof-of-reality Aug 4, 2023
3e50fba
Fix AF
proof-of-reality Aug 4, 2023
360a60b
Update models & add basic functionality
proof-of-reality Aug 6, 2023
446798c
Add groups endpoint
proof-of-reality Aug 6, 2023
d8322fe
Display tags on main tables
proof-of-reality Aug 6, 2023
72e7f7f
Add delete group
proof-of-reality Aug 6, 2023
54dab54
Add basic CRUD for groups
proof-of-reality Aug 7, 2023
71531c3
Fix handling add/rm channels to groups
proof-of-reality Aug 7, 2023
30b13b3
Fix sorting
proof-of-reality Aug 7, 2023
32dad27
Add error messages
proof-of-reality Aug 7, 2023
e1062c2
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Aug 7, 2023
e66142d
Add constraint for multiple groups having conflicting settings
proof-of-reality Aug 9, 2023
bf11352
Fix minimal UI settings
proof-of-reality Aug 11, 2023
444a912
Fix issue while updating default group
proof-of-reality Aug 12, 2023
af7133f
Remove return
proof-of-reality Aug 12, 2023
747e105
Fix Rebalancer
proof-of-reality Aug 13, 2023
0060275
Merge AF calculations
proof-of-reality Aug 13, 2023
bc1af0f
Fix issue with updating values & add link to conflicting field
proof-of-reality Aug 13, 2023
5a20488
Fix retrieving and updating AF settings for a channel/group
proof-of-reality Aug 13, 2023
bc94e73
Add recently discovered channel to `LNDg` defaults
proof-of-reality Aug 13, 2023
b6c9525
Exclude `LNDg` from conflicting settings
proof-of-reality Aug 13, 2023
6fe7923
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Aug 25, 2023
72d26e3
Fix channel creation update
proof-of-reality Oct 10, 2023
dc9d5a0
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Oct 10, 2023
ec90f34
Fix `update_channels` on new channel event
proof-of-reality Oct 11, 2023
77521a3
Fix test
proof-of-reality Oct 11, 2023
4945683
Fix merge issue
proof-of-reality Oct 11, 2023
0cae3a9
Fix getting AF group when AF settings is not assigned yet
proof-of-reality Oct 11, 2023
8b16c43
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Oct 18, 2023
046ed3b
Fixed class name
proof-of-reality Oct 18, 2023
84031a6
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Oct 23, 2023
b3dc6c8
Merge branch 'v1.8.0' into feature/add-tags-to-channels
proof-of-reality Oct 23, 2023
1365493
Fix rebalance scheduling
proof-of-reality Oct 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 19 additions & 44 deletions gui/af.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -115,4 +88,6 @@ def main(channels):


if __name__ == '__main__':
print(main(Channels.objects.filter(is_open=True)))
for g in Groups.objects.prefetch_related('channels').all():
print(f"{datetime.now().strftime('%c')} : [AF] : Running on group: {g.name}")
main(g)
52 changes: 27 additions & 25 deletions gui/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
Original file line number Diff line number Diff line change
@@ -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',
)
]
48 changes: 9 additions & 39 deletions gui/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'

Expand Down
14 changes: 11 additions & 3 deletions gui/serializers.py
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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):
Expand Down
8 changes: 6 additions & 2 deletions gui/static/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand All @@ -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))
}
}
12 changes: 6 additions & 6 deletions gui/static/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading