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 @@