This commit is contained in:
Xavier Fiechter 2024-03-14 17:22:39 +01:00
parent fc903d105f
commit 402c38719b
38 changed files with 1148 additions and 237 deletions

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
db.sqlite3
config.ini
labelbase.log
labelbase.log.*
bgt.log
db/
*.pyc

View file

@ -1,11 +1,11 @@
import random
import time
from django.db import close_old_connections
from django.core.management.base import BaseCommand
import asyncio
from background_task.tasks import tasks, autodiscover
from background_task.utils import SignalManager
from django.db import close_old_connections
from background_task.recovery import start_recovery
import logging

View file

@ -0,0 +1,20 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from datetime import timedelta
import logging
from background_task.models import CompletedTask
logger = logging.getLogger('labelbase')
class Command(BaseCommand):
help = "Remove completed tasks after 1 day."
def handle(self, *args, **options):
try:
threshold = timezone.now() - timedelta(days=1)
deleted_count, _ = CompletedTask.objects.filter(locked_at__lte=threshold).delete()
self.stdout.write(self.style.SUCCESS(f"Deleted {deleted_count} completed tasks."))
except Exception as ex:
logger.exception("Error occurred while deleting completed tasks")
self.stdout.write(self.style.ERROR("An error occurred while deleting completed tasks."))

View file

@ -57,6 +57,79 @@ def is_valid_output_ref(ref):
return False
def checkup_label(label_id, loop):
if label_id and loop:
try:
elem = Label.objects.get(id=label_id)
output = OutputStat.objects.filter(user=elem.labelbase.user,
type_ref_hash=elem.type_ref_hash,
network=elem.labelbase.network).last()
if not output:
output = OutputStat(user=elem.labelbase.user,
type_ref_hash=elem.type_ref_hash,
network=elem.labelbase.network, value=0)
if elem.type == "output" and is_valid_output_ref(elem.ref) and \
(output.spent is not True or output.confirmed_at_block_time == 0):
electrum_hostname = elem.labelbase.user.profile.electrum_hostname or "electrum.emzy.de"
electrum_ports = elem.labelbase.user.profile.electrum_ports or "s50002"
server_info = ServerInfo(electrum_hostname, electrum_hostname, ports=(electrum_ports))
conn = StratumClient()
utxo = elem.ref
utxo_resp = loop.run_until_complete(interact(conn, server_info, "blockchain.transaction.get", utxo))
if utxo_resp:
txid, index, address, value, blocktime = utxo_resp
if blocktime:
HistoricalPrice.get_or_create_from_api(timestamp=blocktime)
unspents = loop.run_until_complete(interact_addr(conn, server_info, "blockchain.address.listunspent", address))
utxo_value = 0
utxo_height = 0
if unspents:
for unspent in unspents:
if unspent.get('tx_hash') == txid and \
unspent.get('tx_pos') == int(index) and \
unspent.get('height') > 0 and \
unspent.get('value') > 0: # Output is confirmed, but not spent yet
output.spent = False
utxo_value = unspent.get('value')
utxo_height = unspent.get('height')
output.network = elem.labelbase.network
if utxo_height:
output.confirmed_at_block_height = utxo_height
if blocktime:
output.confirmed_at_block_time = blocktime
if utxo_value:
output.value = utxo_value
elif value:
output.value = value
break
#
elif conn.last_error:
output.last_error = conn.last_error
else:
output.last_error = {}
output.save()
try:
conn.close()
except:
pass
except Exception as e:
logger.error("Error processing label {}: {}".format(label_id, e))
else:
if not label_id:
logger.error("Can't get label_id! {}".format(label_id))
if not loop:
logger.error("Can't get loop!")
def checkup_label_buggy(label_id, loop):
if label_id and loop:
elem = Label.objects.get(id=label_id)
output = OutputStat.objects.filter(user=elem.labelbase.user,
@ -69,7 +142,8 @@ def checkup_label(label_id, loop):
network=elem.labelbase.network, value=0)
print("Using OutputStat id {}".format(output))
print("elem.type {} {} {} {}".format(elem.type, is_valid_output_ref(elem.ref), elem.ref, output.spent))
if elem.type == "output" and is_valid_output_ref(elem.ref) and output.spent is not True:
if elem.type == "output" and is_valid_output_ref(elem.ref) and \
(output.spent is not True or output.confirmed_at_block_time == 0):
electrum_hostname = elem.labelbase.user.profile.electrum_hostname
if not electrum_hostname:
electrum_hostname = "electrum.emzy.de"
@ -101,6 +175,7 @@ def checkup_label(label_id, loop):
utxo_value = 0
utxo_height = 0
print("unspents: {}".format(unspents))
if unspents:
for unspent in unspents:
if unspent.get('tx_hash') == tx_hash and \

View file

@ -14,6 +14,7 @@ import logging
logger = logging.getLogger('labelbase')
class OutputStat(models.Model):
"""
These fields are unencrypted. Why?

View file

5
django/hashtags/admin.py Normal file
View file

@ -0,0 +1,5 @@
from django.contrib import admin
from .models import Hashtag
admin.site.register(Hashtag)

6
django/hashtags/apps.py Normal file
View file

@ -0,0 +1,6 @@
from django.apps import AppConfig
class HashtagsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'hashtags'

View file

@ -0,0 +1,26 @@
# Generated by Django 3.2.24 on 2024-03-12 10:43
from django.db import migrations, models
import django.db.models.deletion
import django_cryptography.fields
class Migration(migrations.Migration):
initial = True
dependencies = [
('labelbase', '0010_alter_label_label'),
]
operations = [
migrations.CreateModel(
name='Hashtag',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', django_cryptography.fields.encrypt(models.CharField(blank=True, default='', max_length=160))),
('description', django_cryptography.fields.encrypt(models.TextField(blank=True, default=''))),
('labelbase', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='labelbase.labelbase')),
],
),
]

View file

19
django/hashtags/models.py Normal file
View file

@ -0,0 +1,19 @@
from django.db import models
from django.contrib.auth.models import User
from django_cryptography.fields import encrypt
from django.urls import reverse
from labelbase.models import Labelbase
class Hashtag(models.Model):
labelbase = models.ForeignKey(Labelbase, on_delete=models.CASCADE)
name = encrypt(
models.CharField(
max_length=160,
default="",
blank=True,
)
)
description = encrypt(models.TextField(default="", blank=True))
def get_absolute_url(self):
return reverse('hashtag_edit', kwargs={'pk': self.pk})

20
django/hashtags/tasks.py Normal file
View file

@ -0,0 +1,20 @@
import re
from .models import Hashtag
from labelbase.models import Label
from background_task import background
@background(schedule=1)
def store_hashtags_as_objects(labelbase_id, loop=None):
for obj in Label.objects.filter(labelbase_id=labelbase_id):
hashtags = re.findall(r'#\w+', obj.label)
for tag in hashtags:
clean_tag = re.search(r'#(\w+)', tag).group(1)
hts = Hashtag.objects.filter(labelbase_id=labelbase_id)
found = False
for ht in hts:
if ht.name == clean_tag:
found = True
break
if not found:
hashtag = Hashtag(name=clean_tag, labelbase_id=labelbase_id)
hashtag.save()

71
django/hashtags/views.py Normal file
View file

@ -0,0 +1,71 @@
from django.views.generic import ListView
from django.views.generic.edit import UpdateView
from django.views import View
from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import get_object_or_404
from django.contrib import messages
from labelbase.models import Labelbase
from .tasks import store_hashtags_as_objects
from .models import Hashtag
class LabelbaseProxyView(View):
def get(self, request, *args, **kwargs):
labelbase_id = kwargs.get('labelbase_id')
labelbase = get_object_or_404(Labelbase, id=labelbase_id,
user_id=request.user.id)
if request.GET.get('sync', 'no') == 'yes':
store_hashtags_as_objects(labelbase_id)
messages.add_message(
request,
messages.INFO,
"<strong>Processing Hashtags!</strong> Please wait briefly and refresh your page.",
)
return HttpResponseRedirect(labelbase.get_hashtags_url())
class HashtagListView(ListView):
model = Hashtag
template_name = 'hashtags/labelbase_list.html'
def get_queryset(self):
labelbase_id = self.kwargs['labelbase_id']
labelbase = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
queryset = Hashtag.objects.filter(labelbase_id=labelbase_id,
labelbase__user_id=self.request.user.id).order_by('-name')
return queryset
def get_context_data(self, **kwargs):
context = super(HashtagListView, self).get_context_data(**kwargs)
context["active_labelbase_id"] = self.kwargs['labelbase_id']
context["labelbase"] = get_object_or_404(
Labelbase, id=self.kwargs['labelbase_id'], user_id=self.request.user.id
)
return context
class HashtagUpdateView(UpdateView):
model = Hashtag
fields = ['name', 'description']
template_name = 'hashtags/labelbase_edit.html'
def dispatch(self, request, *args, **kwargs):
if self.request.user.id != self.get_object().labelbase.user_id:
return HttpResponseForbidden("You do not have permission to edit this hashtag.")
return super().dispatch(request, *args, **kwargs)
def get_success_url(self):
labelbase_id = self.object.labelbase_id
return reverse('labelbase_hashtags', kwargs={'labelbase_id': labelbase_id})
def get_context_data(self, **kwargs):
labelbase_id = self.get_object().labelbase.id
context = super(HashtagUpdateView, self).get_context_data(**kwargs)
context["active_labelbase_id"] = labelbase_id
context["labelbase"] = get_object_or_404(
Labelbase, id=labelbase_id, user_id=self.request.user.id
)
return context

View file

@ -0,0 +1,28 @@
# Generated by Django 3.2.24 on 2024-03-14 09:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('knowledge_base', '0003_auto_20240308_2231'),
]
operations = [
migrations.AddField(
model_name='article',
name='order',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='category',
name='desc',
field=models.CharField(default='', max_length=280),
),
migrations.AddField(
model_name='category',
name='order',
field=models.IntegerField(default=0),
),
]

View file

@ -14,6 +14,8 @@ class ExportSnapshot(models.Model):
class Category(models.Model):
name = models.CharField(max_length=100)
desc = models.CharField(max_length=280, default="")
order = models.IntegerField(default=0)
slug = models.SlugField(unique=True, max_length=100)
exclude_from_export = models.BooleanField(default=False)
@ -27,6 +29,7 @@ class Category(models.Model):
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
order = models.IntegerField(default=0)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
slug = models.SlugField(unique=True, max_length=100)
exclude_from_export = models.BooleanField(default=False)

View file

@ -8,6 +8,5 @@ urlpatterns = [
path('article/<slug:slug>/', ArticleDetailView.as_view(), name='article_detail'),
#TODO: staff only,
path('export', ExportJSONView.as_view(), name='export_json'),
#TODO: staff only,
path('import', ImportJSONView.as_view(), name='import_json'),
]

View file

@ -13,7 +13,6 @@ class ExportJSONView(View):
for category in categories:
articles = list(category.article_set.all().exclude(exclude_from_export=True).values())
data[category.name] = articles
export_snapshot = ExportSnapshot.objects.create(
exported_data=data
)
@ -37,7 +36,8 @@ class ImportJSONView(View):
try:
category = Category.objects.get(slug=slugify(category_name))
except Category.DoesNotExist:
category = Category.objects.create(name=category_name, slug=slugify(category_name))
category = Category.objects.create(name=category_name,
slug=slugify(category_name))
for article_data in articles:
try:
@ -47,8 +47,10 @@ class ImportJSONView(View):
article.category = category
article.save()
except Article.DoesNotExist:
Article.objects.create(title=article_data['title'], content=article_data['content'], category=category, slug=article_data['slug'])
Article.objects.create(title=article_data['title'],
content=article_data['content'],
category=category,
slug=article_data['slug'])
return JsonResponse({'message': 'Import successful'})
except json.JSONDecodeError:
return JsonResponse({'error': 'Invalid JSON format'}, status=400)

View file

@ -108,6 +108,8 @@ class Labelbase(models.Model):
def get_absolute_url(self):
return reverse("labelbase", args=[self.id])
def get_hashtags_url(self):
return reverse('labelbase_hashtags', kwargs={'labelbase_id': self.id})
class Label(models.Model):
"""
@ -193,6 +195,7 @@ class Label(models.Model):
"""
return self.labelbase.get_absolute_url()
def get_mempool_url(self):
if self.labelbase and self.labelbase.network != "mainnet":
mempool_endpoint = \

View file

@ -237,7 +237,7 @@ box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
--bd-callout-border: var(--bs-warning-border-subtle);
}
.bd-callout-good {
.bd-callout-good {
--bd-callout-color: var(--bs-success-text-emphasis);
--bd-callout-bg: #d9fbd0;
--bd-callout-border: #bee8be;
@ -285,17 +285,10 @@ box-shadow: 0 0 0 3px rgba(255, 255, 255, .25);
}
.modal-footer {
border-top: 0;
}
.modal-header {
border-bottom: 0;
}
/*
<style>
.help-block {
display: none;
.modal-footer {
border-top: 0;
}
</style>*/
.modal-header {
border-bottom: 0;
}

View file

@ -109,6 +109,7 @@ INSTALLED_APPS = [
"background_task",
"connectrum",
"knowledge_base",
"hashtags",
]

View file

@ -12,6 +12,8 @@ from userprofile.views import (ProfileView,
ElectrumInfoUpdateView)
from userprofile.views import APIKeyView
from hashtags.views import HashtagListView, HashtagUpdateView, LabelbaseProxyView
from .views import (
LabelbaseView,
@ -27,7 +29,7 @@ from .views import (
RegistrationView,
LabelbaseFormView,
LabelbaseUpdateView,
FaqView,
DonationView,
AboutView,
EncryptionView,
InteroperationalView,
@ -37,6 +39,7 @@ from .views import (
FixAndMergeLabelsView,
LabelbaseDatatableView,
LabelbasePortfolioView,
OutputStatUpdateRedirectView,
)
from importer.views import upload_labels
@ -99,6 +102,21 @@ urlpatterns = [
login_required(LabelbaseView.as_view()),
name="labelbase"
),
path(
"labelbase/<int:labelbase_id>/hashtags/",
login_required(HashtagListView.as_view()),
name="labelbase_hashtags"
),
path(
"labelbase/<int:labelbase_id>/hashtags/proxy/",
login_required(LabelbaseProxyView.as_view()),
name="labelbase_hashtags_proxy"
),
path(
"labelbase/hashtag/<int:pk>/edit/",
login_required(HashtagUpdateView.as_view()),
name="hashtag_edit"
),
path(
"labelbase/<int:labelbase_id>/data/",
login_required(LabelbaseDatatableView.as_view()),
@ -206,9 +224,9 @@ urlpatterns = [
name="terms"
),
path(
"faq",
FaqView.as_view(),
name="faq"
"donate",
DonationView.as_view(),
name="donate"
),
path(
"about",
@ -225,6 +243,11 @@ urlpatterns = [
InteroperationalView.as_view(),
name="interoperational"
),
path(
"outputstat/<int:output_stats_id>/update/<int:label_id>/",
login_required(OutputStatUpdateRedirectView.as_view()),
name='outputstat_update_redirect'
),
path(
"",
HomeView.as_view(),

View file

@ -12,24 +12,24 @@ from django.views.generic.list import ListView
from django.views.generic.edit import DeleteView
from django.views.generic.edit import UpdateView
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.http import FileResponse
from django.urls import reverse
from django.template.loader import render_to_string
from django.contrib import messages
from django_datatables_view.base_datatable_view import BaseDatatableView
from two_factor.views import OTPRequiredMixin
from two_factor.views.utils import class_view_decorator
from rest_framework.authtoken.models import Token
from bip329.bip329_writer import BIP329JSONLWriter, BIP329JSONLEncryptedWriter
from labelbase.models import Label, Labelbase
from labelbase.forms import LabelForm, LabelbaseForm
from labelbase.forms import ExportLabelsForm
from django.http import HttpResponseRedirect
from django.http import FileResponse
from rest_framework.authtoken.models import Token
from bip329.bip329_writer import BIP329JSONLWriter, BIP329JSONLEncryptedWriter
from django.urls import reverse
from django_datatables_view.base_datatable_view import BaseDatatableView
from django.template.loader import render_to_string
from django.contrib import messages
from .utils import hashtag_to_badge, extract_fiat_value
from finances.models import OutputStat
from finances.tasks import check_all_outputs
from .utils import hashtag_to_badge, extract_fiat_value
class AboutView(TemplateView):
@ -56,8 +56,8 @@ class TermsView(TemplateView):
template_name = "terms.html"
class FaqView(TemplateView):
template_name = "faq.html"
class DonationView(TemplateView):
template_name = "donate.html"
class LabelDeleteView(DeleteView):
@ -648,3 +648,49 @@ class RegistrationCompleteView(TemplateView):
@class_view_decorator(never_cache)
class ExampleSecretView(OTPRequiredMixin, TemplateView):
template_name = "secret.html"
class OutputStatUpdateRedirectView(View):
def get(self, request, output_stats_id, label_id):
try:
output_stat = OutputStat.objects.get(id=output_stats_id)
except OutputStat.DoesNotExist:
messages.add_message(
request,
messages.ERROR,
"<strong>Hmmmm....</srong> Could not modify label data."
)
return redirect('edit_label', pk=label_id)
try:
label = Label.objects.get(id=label_id, labelbase__user_id=self.request.user.id)
except Label.DoesNotExist:
messages.add_message(
request,
messages.ERROR,
"Can't find what you are looking for.."
)
return redirect("home")
spent = request.GET.get('force-spent', None)
if spent not in ["true", "false", "none"]:
messages.add_message(
request,
messages.ERROR,
"<strong>Hmmmm....</srong> This action is unknown."
)
return redirect('edit_label', pk=label_id)
if spent == "true":
spent = True
elif spent == "false":
spent = False
elif spent == "none":
spent = None
OutputStat.objects.filter(id=output_stat.id).update(spent=spent)
messages.add_message(
request,
messages.SUCCESS,
"<strong>Okay!</strong> Verifying output status now."
)
label.save() # will trigger a check agains Electrum
return redirect('edit_label', pk=label_id)

View file

@ -15,8 +15,11 @@
<link href="{% static 'css/dataTables.bootstrap5.min.css' %}" rel="stylesheet">
<link href="{% static 'css/labelbase.css' %}" rel="stylesheet">
{% block extra_media %}{% endblock %}
<style media="screen">
{% block css %}{% endblock %}
{% render_block "css" %}
</style>
</head>
@ -120,7 +123,7 @@
<li><a class="nav-link" style="font-size: .875rem;"><span data-feather="dollar-sign" class="align-text-bottom"></span> Fiat Finances</a></li>
{% endif %}
{% if labelbase.user.profile.use_hashtags %}
<li><a class="nav-link" style="font-size: .875rem;"><span data-feather="hash" class="align-text-bottom"></span> Hashtags</a></li>
<li><a href="{{ labelbase.get_hashtags_url }}" class="nav-link" style="font-size: .875rem;"><span data-feather="hash" class="align-text-bottom"></span> Hashtags</a></li>
{% endif %}
</ul>
@ -178,15 +181,17 @@
<span>Community</span>
</h6>
<li class="nav-item ">
<a class="nav-link" href="{% url "donate" %}"><span data-feather="heart" class="align-text-bottom"></span> Donate</a>
<li>
<li class="nav-item ">
<a class="nav-link" href="https://labelbase.space/newsletter/"><span data-feather="mail" class="align-text-bottom"></span> Newsletter</a>
<li>
<li class="nav-item ">
<!--li class="nav-item ">
<a class="nav-link" href="https://labelbase.space/#/"><span data-feather="mic" class="align-text-bottom"></span> Podcast</a>
<li>
<li-->
<li class="nav-item ">
<a class="nav-link" href="https://github.com/Labelbase/Labelbase/"><span data-feather="github" class="align-text-bottom"></span> Code on GitHub</a>
@ -205,7 +210,7 @@
<li>
-->
<li class="nav-item ">
<a class="nav-link" href="{% url 'faq' %}"><span data-feather="book" class="align-text-bottom"></span> FAQ & Knowledge</a>
<a class="nav-link" href="{% url 'kb-index' %}"><span data-feather="book" class="align-text-bottom"></span> Knowledge Base</a>
<li>
<li class="nav-item ">
@ -222,18 +227,10 @@
</ul>
{% comment %}
<ul class="nav flex-column mb-2">
<li class="nav-item ">
<a class="nav-link" href="{% url 'terms' %}"><span data-feather="heart" class="align-text-bottom" style="color:red;"></span> Special Thanks</a>
<li>
</ul>
<br>
{% endcomment %}
</div>
</div>
</nav>
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{% include "_messages.html" %}
@ -453,7 +450,7 @@
data: { toggleId: 'use_chatwoot', toggleValue: 'true' },
success: function (response) {
show_message("success", "<strong>Success!</strong> " + response.message)
window.$chatwoot.toggle("open");
window.$chatwoot.toggle("open");
},
error: function (xhr, status, error) {
console.error(error);

View file

@ -1,5 +1,7 @@
<style>
{% load sekizai_tags %}
<style>
{% addtoblock "css" %}
.carousel-control-prev {
margin-left:-2rem;
}
@ -8,7 +10,6 @@
margin-left: 0rem;
}
.carousel-control-prev-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%237e7e7e'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e");
}
@ -18,7 +19,6 @@
margin-right: -4em;
}
.carousel-indicators {
list-style: none;
display: flex;
@ -28,34 +28,25 @@
padding-bottom: 1em;
}
/* .carousel-indicators li {
background-color: #ccc !important;
border: 2px solid #007bff;
margin: 0 5px !important;
width: 12px;
height: 12px;
cursor: pointer;
} */
.carousel-indicators .active {
background-color: #7e7e7e; /* Adjust the color for the active indicator */
background-color: #7e7e7e;
border: 1px solid #7e7e7e;
}
.carousel-inner {
padding-left: 2em;
padding-right: 0em;
}
</style>
{% endaddtoblock %}
</style>
<div class="modal fade" id="introTourModal" tabindex="-1" aria-labelledby="introTourModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="introTourModalLabel">Welcome to Labelbase!</h5>
<h3 class="modal-title" id="introTourModalLabel">Welcome to Labelbase!</h3>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
@ -64,7 +55,7 @@
Some key details to bring to your attention.
</p>
<br>
<div id="carouselExample" class="carousel slide" data-bs-ride="carousel">
<div id="carouselIntroduction" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner">
<div class="carousel-item active">
@ -87,8 +78,8 @@
</div>
<div class="carousel-item">
<h5>Tree maps and Analytics</h5>
<p>Access detailed tree maps and analytics to make data-driven decisions which UTXO to spend next.</p>
<h5>Tree Maps and Analytics</h5>
<p>Access detailed Tree Maps and analytics to make data-driven decisions which UTXO to spend next.</p>
</div>
@ -99,14 +90,21 @@
</div>
<div class="carousel-item">
<h5>Donate</h5>
<p>Before upgrading to a newer version, backup your data by exporting your labels as a BIP-329 file or an encrypted archive.</p>
</div>
</div>
<button class="carousel-control-prev" type="button" data-bs-target="#carouselExample" data-bs-slide="prev">
<button class="carousel-control-prev" type="button" data-bs-target="#carouselIntroduction" data-bs-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#carouselExample" data-bs-slide="next">
<button class="carousel-control-next" type="button" data-bs-target="#carouselIntroduction" data-bs-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
@ -114,10 +112,10 @@
</div>
<div class="modal-footer">
<ol class="carousel-indicators">
<li data-bs-target="#carouselExample" data-bs-slide-to="0"></li>
<li data-bs-target="#carouselExample" data-bs-slide-to="1"></li>
<li data-bs-target="#carouselExample" data-bs-slide-to="2"></li>
<li data-bs-target="#carouselExample" data-bs-slide-to="3"></li>
<li data-bs-target="#carouselIntroduction" data-bs-slide-to="0"></li>
<li data-bs-target="#carouselIntroduction" data-bs-slide-to="1"></li>
<li data-bs-target="#carouselIntroduction" data-bs-slide-to="2"></li>
<li data-bs-target="#carouselIntroduction" data-bs-slide-to="3"></li>
</ol>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Get Started</button>
</div>

View file

@ -55,5 +55,4 @@
Your feedback is important to us. :)
</p>
{% endblock %}
api_token
{% endblock %}

View file

@ -0,0 +1,121 @@
{% extends "_base.html" %}
{% load sekizai_tags %}
{% load i18n %}
{% block title %}Donate Support Labelbase{% endblock %}
{% block nav_home %}active{% endblock %}
{% block content %}
<div class=" p-3 pb-md-4 mx-auto text-left">
<h2 class="display-8 fw-normal">Support Labelbase: Make a Difference with Your Donation</h2>
<br>
<p class="fs-5 text-muted">Every Contribution Helps Us Build a Better Bitcoin Labeling Experience</p>
<p class="fs-5 text-muted">
Choose the amount you'd like to donate to Labelbase.
</p> <p class="fs-5 text-muted">
Your generosity ensures that our project continues to thrive and evolve, directly supporting our mission to make Bitcoin transactions more organized and transparent.
</p> <p class="fs-5 text-muted">
Thank you for your support!
</p>
<style>
{% addtoblock "css" %}
.btcpay-form { display: inline-flex; align-items: center; justify-content: center; } .btcpay-form--inline { flex-direction: row; } .btcpay-form--block { flex-direction: column; } .btcpay-form--inline .submit { margin-left: 15px; } .btcpay-form--block select { margin-bottom: 10px; } .btcpay-form .btcpay-custom-container{ text-align: center; }.btcpay-custom { display: flex; align-items: center; justify-content: center; } .btcpay-form .plus-minus { cursor:pointer; font-size:25px; line-height: 25px; background: #DFE0E1; height: 30px; width: 45px; border:none; border-radius: 60px; margin: auto 5px; display: inline-flex; justify-content: center; } .btcpay-form select { -moz-appearance: none; -webkit-appearance: none; appearance: none; color: currentColor; background: transparent; border:1px solid transparent; display: block; padding: 1px; margin-left: auto; margin-right: auto; font-size: 11px; cursor: pointer; } .btcpay-form select:hover { border-color: #ccc; } .btcpay-form option { color: #000; background: rgba(0,0,0,.1); } .btcpay-input-price { -moz-appearance: textfield; border: none; box-shadow: none; text-align: center; font-size: 25px; margin: auto; border-radius: 5px; line-height: 35px; background: #fff; }.btcpay-input-price::-webkit-outer-spin-button, .btcpay-input-price::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
{% endaddtoblock %}
</style>
<form method="POST" action="http://178.194.166.112:3003/api/v1/invoices" class="btcpay-form btcpay-form--block">
<input type="hidden" name="storeId" value="77qPp3s5bGFQMfuStNWuWXmdKfn4t4tSQpvucQ4UdzKx" />
<input type="hidden" name="jsonResponse" value="true" />
<input type="hidden" name="notifyEmail" value="labelbase.space@proton.me" />
<div class="btcpay-custom-container">
<div class="btcpay-custom">
<input class="btcpay-input-price" type="number" name="price" min="1" max="210000" step="1" value="1" data-price="1" style="width:2em;" />
</div>
<select name="currency">
<option value="USD" selected>USD</option>
<option value="GBP">GBP</option>
<option value="EUR">EUR</option>
<option value="BTC">BTC</option>
</select>
</div>
<button type="submit" class="submit" name="submit" style="min-width:146px;min-height:40px;border-radius:4px;border-style:none;background-color:#0f3b21;cursor:pointer;" title="Pay with BTCPay Server, a Self-Hosted Bitcoin Payment Processor"><span style="color:#fff">Donate with</span>
<img src="http://178.194.166.112:3003/img/paybutton/logo.svg" style="height:40px;display:inline-block;padding:5% 0 5% 5px;vertical-align:middle;">
</button></form>
</div>
{% addtoblock "js" %}
if (!window.btcpay) {
var script = document.createElement('script');
script.src = "http://178.194.166.112:3003/modal/btcpay.js";
document.getElementsByTagName('head')[0].append(script);
}
function handleFormSubmit(event) {
event.preventDefault();
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200 && this.responseText) {
window.btcpay.appendInvoiceFrame(JSON.parse(this.responseText).invoiceId);
}
};
xhttp.open('POST', event.target.getAttribute('action'), true);
xhttp.send(new FormData(event.target));
}
document.querySelectorAll(".btcpay-form").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('submit', handleFormSubmit);
el.dataset.initialized = true;
}
});
function handlePlusMinus(event) {
event.preventDefault();
const root = event.target.closest('.btcpay-form');
const el = root.querySelector('.btcpay-input-price');
const step = parseInt(event.target.dataset.step) || 1;
const min = parseInt(event.target.dataset.min) || 1;
const max = parseInt(event.target.dataset.max);
const type = event.target.dataset.type;
const price = parseInt(el.value) || min;
if (type === '-') {
el.value = price - step < min ? min : price - step;
} else if (type === '+') {
el.value = price + step > max ? max : price + step;
}
}
document.querySelectorAll(".btcpay-form .plus-minus").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('click', handlePlusMinus);
el.dataset.initialized = true;
}
});
function handlePriceInput(event) {
event.preventDefault();
const root = event.target.closest('.btcpay-form');
const price = parseInt(event.target.dataset.price);
if (isNaN(event.target.value)) root.querySelector('.btcpay-input-price').value = price;
const min = parseInt(event.target.getAttribute('min')) || 1;
const max = parseInt(event.target.getAttribute('max'));
if (event.target.value < min) {
event.target.value = min;
} else if (event.target.value > max) {
event.target.value = max;
}
}
document.querySelectorAll(".btcpay-form .btcpay-input-price").forEach(function(el) {
if (!el.dataset.initialized) {
el.addEventListener('input', handlePriceInput);
el.dataset.initialized = true;
}
});
{% endaddtoblock %}
{% endblock %}

View file

@ -5,13 +5,7 @@
{% block nav_home %}active{% endblock %}
{% block content %}
<style>
.lb-header {
max-width: 810px;
}
</style>
<div class="p-3 pb-md-4 mx-auto text-center">
<h1 style="padding-top: 1.6em; padding-bottom: 0.9em; text-transform: uppercase;font-weight:800;">L<span style="height:1.1em; width:1em" data-feather="tag" class="align-text-bottom"></span>belbase</h1>
<h2 class="display-6 fw-normal" style="padding-bottom: 0.9em;">All your labels in one place.</h2>

View file

@ -1,75 +0,0 @@
{% extends "_base.html" %}
{% load i18n %}
{% block title %}Privacy{% endblock %}
{% block nav_home %}active{% endblock %}
{% block content %}
<div class=" p-3 pb-md-4 mx-auto text-left">
<h2 class="display-8 fw-normal">Frequently Asked Questions</h2>
<p>
</p>
<h3 class="display-10 fw-normal">How to import labels from my wallet into Labelbase?</h3>
<p class="fs-5 text-muted">
To import data from various wallet's export format into Labelbase and then export it as BIP-329 labels, you would need to follow these steps:
<ol>
<li>
Export data from your wallet: The first step is to export your data from your wallet. The format of this export will depend on the wallet you are using, but it may include private keys, public keys, or other information.
</li>
<li>
Import data into Labelbase: Next, you'll need to import the exported data into Labelbase. You can do this by using the import feature within your Labelbase. Choose the appropriate option based on the file format you want to import.
Even through we do not store your uploaded files, <br>DO NOT UPLOAD ANY PRIVATE KEY MATERIAL TO LABELBASE.
</li>
<li>
Modify and edit labels: Once your labels are imported into Labelbase, you can modify and edit your labels as needed. This may include changing the names of your addresses, grouping them into different categories, or adding additional information.
</li>
<li>
Export as BIP-329 labels: Finally, you'll need to export your labels from Labelbase as BIP-329 labels. This will typically involve downloading a file in the BIP-329 format, which you can then import into your wallet.
</li>
<li>
Use Labelbase through the API: Alternatively, you can use Labelbase through the API in your wallet. This will allow you to access your labels directly from within your wallet, without the need to export and import files. To do this, your wallet needs to integrate the Labelbase API into the wallet.
</li>
</ol>
</p>
<!-- -->
<h3 class="display-10 fw-normal">How is the encrypted label file generated, and how can I decrypt it?
</h3>
<p class="fs-5 text-muted">
<ul>
<li>The encrypted label file is generated using the <a href="https://github.com/Labelbase/python-bip329">Labelbase Python library for BIP-329</a>. This library provides functionality for managing Bitcoin coin control information and allows you to export labels in an encrypted format.
</li><li>
To decrypt the BIP-329 label files manually, you can refer to <a href="https://github.com/Labelbase/python-bip329#decrypting-bip-329-label-files">the instructions provided</a> in the Labelbase Python library documentation. This documentation provides detailed steps and examples for decrypting label files using the library.
</li><li>
Please note that the passphrase used for encryption is crucial for decryption. Ensure that you keep your passphrase secure and do not share it with anyone unauthorized, as it is required to access your encrypted label data.
</li>
</ul>
<!-- -->
<h3 class="display-10 fw-normal">How is the encrypted label file generated, and how can I decrypt it?
</h3>
<p class="fs-5 text-muted">
<ul>
<li>
The encrypted label file is generated using the <a href="https://github.com/Labelbase/python-bip329">Labelbase Python library for BIP-329</a>. This library provides functionality for managing Bitcoin coin control information and allows you to export labels in an encrypted format.
</li>
<li>
To decrypt the BIP-329 label files manually, you can refer to <a href="https://github.com/Labelbase/python-bip329#decrypting-bip-329-label-files">the instructions provided</a> in the Labelbase Python library documentation. This documentation provides detailed steps and examples for decrypting label files using the library.
</li>
<li>
Please note that the passphrase used for encryption is crucial for decryption. Ensure that you keep your passphrase secure and do not share it with anyone unauthorized, as it is required to access your encrypted label data.
</li>
</ul>
</div>
{% endblock %}

View file

@ -0,0 +1,40 @@
{% if labelbase %}
<div class="row">
<div class="col float-start">
<h2 style="padding-top: 1em;">{% block title %}{{ labelbase.name }} {% endblock %}</h2>
{% if labelbase.fingerprint %}<tt>{{ labelbase.fingerprint }} </tt>{% endif %}
{% if labelbase.about %}
<p>{{ labelbase.about}}</p>
{% endif %}
</div>
<div class="col float-end" >
<div class="btn-group float-end" role="group" style="padding-top: 2em;">
{% comment %}
<button type="button" class="rounded-start btn btn-sm btn-outline-secondary "
data-bs-toggle="modal" data-bs-target="#addLabelModal">New Hashtag</button>
{% endcomment %}
<div class="btn-group" role="group">
<button type="button" class="{#d-none d-md-block #} btn btn-sm btn-outline-secondary dropdown-toggle rounded-end me-2" data-bs-toggle="dropdown" aria-expanded="false">
Hashtag Actions
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{% url "labelbase_hashtags_proxy" labelbase_id=labelbase.id %}?sync=yes">
Collect & Sync Hashtags
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
{% else %}
<p>There is no such labelbase.</p>
{% endif %}

View file

@ -0,0 +1,32 @@
{% extends "_base.html" %}
{% load bootstrap %}
{% load i18n %}
{% load sekizai_tags %}
{% block content %}
<ul class="nav nav-tabs" style="padding-top: 2rem; ">
<li class="nav-item">
<a class="nav-link active" aria-current="page">Hashtag Detail</a>
</li>
</ul>
<div style="padding-top: 2.1rem; padding-bottom: 2.1rem; " >
<form method="post">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<div style="padding-top: 1em;">
<a href="{{ form.instance.labelbase.get_hashtags_url }}"
type="button" class="btn btn-link">Back to Hashtags</a>
<!--a href="#" data-bs-toggle="modal" data-bs-target="#deleteHashtagModal"
type="button" class="btn btn-danger ">Delete</a-->
<input type="submit" class="btn btn-primary" value="Save">
</div>
</form>
</div>
<script>
{% addtoblock "js" %}
{% endaddtoblock %}
</script>
{% endblock %}

View file

@ -0,0 +1,38 @@
{% extends "_base.html" %}
{% load i18n %}
{% load labelbase_tags %}
{% load importer_tags %}
{% load sekizai_tags %}
{% load bootstrap %}
{% block content %}
{% include "hashtags/_labelbase_header_info_menu_hashtag_list.html" %}
<h4>Known Hashtags</h4>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Hashtag</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
{% for hashtag in object_list %}
<tr>
<td><a href="{{ hashtag.get_absolute_url }}">{{ hashtag.pk }}{# <small>(edit)</small>#}</a></td>
<td><a href="{% url "labelbase" labelbase_id=active_labelbase_id %}?tag={{ hashtag.name }}" class="badge badge-hashtag">{{ hashtag.name }}</a></td>
<td>{{ hashtag.description }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

View file

@ -333,6 +333,12 @@
</p>
<a href="{{ reg_url }}" type="button" class="btn btn-lg btn-primary" style="margin-bottom:6rem;">Get started</a>
<br><br>
Built with support from <a href="https://opensats.org/blog/bitcoin-grants-july-2023">OpenSats</a>.
<br><br>
</div>
{% endif %}

View file

@ -4,14 +4,7 @@
{% block title %}Interoperability{% endblock %}
{% block nav_home %}active{% endblock %}
{% block content %}
<style>
.lb-header {
max-width: 810px;
}
</style>
{% block content %}
<div class="p-3 pb-md-4 mx-auto text-center">
<h1 style="padding-top: 1.6em; padding-bottom: 0.9em; text-transform: uppercase;font-weight:800;">L<span style="height:1.1em; width:1em" data-feather="tag" class="align-text-bottom"></span>belbase</h1>
<h2 class="display-6 fw-normal" style="padding-bottom: 0.9em;">All your labels in one place.</h2>

View file

@ -3,14 +3,40 @@
{% load breadcrumb_tags %}
{% load i18n %}
{% load static %}
{% load sekizai_tags %}
{% block title %}{{ article.title }} | Labelbase{% endblock %}
{#% block nav_home %}active{% endblock %#}
{% block content %}
<style>
{% addtoblock "css" %}
pre {
background-color: #f4f4f4;
border: 1px solid #dddddd;
border-left: 3px solid #ccc;
color: #666;
page-break-inside: avoid;
font-family: monospace;
font-size: 15px;
line-height: 1.6;
margin-bottom: 1.6em;
max-width: 100%;
overflow: auto;
padding: 1em;
display: block;
word-wrap: break-word;
white-space: pre-wrap;
}
code {
font-family: inherit;
color: inherit;
padding: 0;
background-color: transparent;
border: none;
}
{% endaddtoblock %}
</style>

View file

@ -9,7 +9,7 @@
{% block content %}
{% breadcrumbs_category category as current_breadcrumbs %}
<nav style="padding-top: 0.76rem; --bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E&#34;);" aria-label="breadcrumb">
@ -24,17 +24,11 @@
</ol>
</nav>
<!-- <ol class="breadcrumb">
<li class="breadcrumb-item"><a href="#">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">Library</li>
</ol>
</nav>
-->
<h2>{{ category.name }}</h2>
<ul>
<ul class="list-group list-group-flush">
{% for article in category.article_set.all %}
<li><a href="{% url 'article_detail' article.slug %}">{{ article.title }}</a></li>
<li class="list-group-item"><a href="{% url 'article_detail' article.slug %}">{{ article.title }}</a></li>
{% endfor %}
</ul>

View file

@ -1,19 +1,81 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Knowledge Base</title>
</head>
<body>
<h1>Categories</h1>
<ul>
{% for category in categories %}
<li><a href="{% url 'category_detail' category.slug %}">{{ category.name }}</a></li>
{% empty %}
Your knowledge base is empty. You can download the latest knowledge here and import it.
{% endfor %}
</ul>
</body>
</html>
{% extends "_base.html" %}
{% load markdown_extras %}
{% load breadcrumb_tags %}
{% load i18n %}
{% load static %}
{% load sekizai_tags %}
{% block title %}Knowledge Base Categories | Labelbase{% endblock %}
{#% block nav_home %}active{% endblock %#}
'kb-index'
{% block content %}
<style>
{% addtoblock "css" %}
.card:hover {
transform: scale(1.05);
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
}
.card-body:hover {
background-color: #f8f9fa; /* or any light color of your choice */
}
{% endaddtoblock %}
</style>
<nav style="padding-top: 0.76rem; --bs-breadcrumb-divider: url(&#34;data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E&#34;);" aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item active" aria-current="page">Knowledge Base</li>
</ol>
</nav>
<div class="p-3 pb-md-4 mx-auto text-center">
<h2 class="display-8 fw-bold text-muted" style="padding-bottom: 0.9em;">Knowledge Base</h2>
<p class="lb-header mx-auto text-center fs-5 text-muted">Browse through the categories below.</p>
<p class="lb-header mx-auto text-center fs-5 text-muted">If you have unanswered questions, are confused, or just want to understand
Labelbase better just get in touch using our {% if request.user.profile.use_chatwoot %}
<a href="JavaScript:void(0);" onclick="window.$chatwoot.toggle('open');">
support chat</a>.
{% else %}
<a href="JavaScript:void(0);" onclick="$('#chatModal').modal('show');">
support chat</a>.
{% endif %}</p>
</div>
<div class="row">
{% for category in categories %}
{% if category.article_set.count %}
<div class="col-sm-6">
<a href="{% url 'category_detail' category.slug %}" style="text-decoration: none; color: inherit;">
<div class="card" style="margin-bottom: 1rem !important; transition: transform 0.2s, box-shadow 0.2s;">
<div class="card-body text-center" style="transition: background-color 0.2s;">
<h5 class="card-title">{{ category.name }}</h5>
<p class="card-text">{{ category.desc }}</p>
</div>
</div>
</a>
</div>
{% endif %}
{% empty %}
Your knowledge base is empty. You can download the latest knowledge here and import it.
{% endfor %}
</div>
{% endblock %}

View file

@ -75,46 +75,47 @@
<div style="padding-top: 0rem; padding-bottom: 2.1rem; ">
<div class="alert bd-callout bd-callout-danger ">
<div class="btn-group" role="group" style="position: absolute; top: -8px; right: -4px; padding: 1.25rem 1rem;">
<button type="button" class="btn btn-sm btn-outline-danger dropdown-toggle " data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Reset output and check again</a></li>
<li><a class="dropdown-item" href="#">Mark output as spent</a></li>
<li><a class="dropdown-item" href="#">Mark output as unspent</a></li>
</ul>
</div>
<div style=" padding-right: 1.8rem"><strong>Output spent! </strong> Blockchain records indicate that this output has been spent in another transaction.
</div>
</div>
{% if output.last_error %}
<div class="bd-callout bd-callout-danger">
<div class="alert bd-callout bd-callout-danger">
<div class="btn-group" role="group" style="position: absolute; top: -8px; right: -4px; padding: 1.25rem 1rem;">
<button type="button" class="btn btn-sm btn-outline-danger dropdown-toggle " data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'outputstat_update_redirect' output_stats_id=output.id label_id=form.instance.id %}?force-spent=none">Verify output status</a></li>
<li><a class="dropdown-item" href="{% url 'outputstat_update_redirect' output_stats_id=output.id label_id=form.instance.id %}?force-spent=true">Mark output as spent</a></li>
<li><a class="dropdown-item" href="{% url 'outputstat_update_redirect' output_stats_id=output.id label_id=form.instance.id %}?force-spent=false">Mark output as unspent</a></li>
</ul>
</div>
<strong>Ouch!</strong> Got "{{ output.last_error.message }}{% if output.last_error.code %} (code {{ output.last_error.code }}){% endif %}" from Electrum.
</div>
{% else %}
{% if form.instance.type == "output" %}
{% switch output.get_spent_status %}
{% case "spent" %}
<div class="bd-callout bd-callout-warning">
<strong>Output spent!</strong> Blockchain records indicate that this output has been spent in another transaction.
</div>
{% case "unspent" %}
<div class="bd-callout bd-callout-good">
<strong>Output unspent!</strong> Blockchain records indicate that this output has not been spent yet.
</div>
{% case "unconfirmed" %}
<div class="bd-callout bd-callout-info">
<strong>Output unconfirmed!</strong> Blockchain records indicate that this output has not been confirmed yet.
</div>
{% endswitch %}
{% endif %}
{% endif %}
{% if form.instance.type == "output" %}
{% switch output.get_spent_status %}
{% case "spent" %}
<div class="bd-callout bd-callout-warning">
<strong>Output spent!</strong> Blockchain records indicate that this output has been spent in another transaction.
</div>
{% case "unspent" %}
<div class="bd-callout bd-callout-good">
<strong>Output unspent!</strong> Blockchain records indicate that this output has not been spent yet.
</div>
{% case "unconfirmed" %}
<div class="alert bd-callout bd-callout-info">
<div class="btn-group" role="group" style="position: absolute; top: -8px; right: -4px; padding: 1.25rem 1rem;">
<button type="button" class="btn btn-sm btn-outline-info dropdown-toggle " data-bs-toggle="dropdown" aria-expanded="false">
Actions
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{% url 'outputstat_update_redirect' output_stats_id=output.id label_id=form.instance.id %}?force-spent=none">
Verify output status</a></li>
</ul>
</div>
<strong>Output unconfirmed!</strong> Blockchain records indicate that this output has not been confirmed yet.
</div>
{% endswitch %}
{% endif %}
<!--div class="bd-callout bd-callout-warning">
<strong>Heads up!</strong> There are multiple records for this transaction output. <a href="">Review & merge</a>

View file

@ -0,0 +1,343 @@
{% extends "_base.html" %}
{% load i18n %}
{% load labelbase_tags %}
{% load importer_tags %}
{% load sekizai_tags %}
{% load bootstrap %}
{% block content %}
{#% include "_labelbase_header_info_menu.html" %#}
{# TODO: Models / Forms aren't loaded if included like that ^^^  using a simplified header #}
{# TODO: Fix & merge is a bit a mess, rework this ASAP. #}
{% if labelbase %}
<div class="row">
<div class="col float-start">
<h2 style="padding-top: 1em;">{% block title %}{{ labelbase.name }} {% endblock %}</h2>
{% if labelbase.fingerprint %}<tt>{{ labelbase.fingerprint }} </tt>{% endif %}
{% if labelbase.about %}
<p>{{ labelbase.about}}</p>
{% endif %}
</div>
</div>
{% else %}
<p>There is no such labelbase.</p>
{% endif %}
{# simplified header #}
<div class="col">
{% if fix_suggestions %}
{% if fix_suggestions == 1 %}
<strong>There is {{ fix_suggestions }} suggestsion!</strong>
{% else %}
<strong>There are {{ fix_suggestions }} suggestions!</strong>
{% endif %}
<!--
<div class="alert alert-dismissible bd-callout bd-callout-info ">
<div style=" padding-right: 1.8rem"><strong>There are {{ fix_suggestions }} suggestions!</strong>
"Automatically Consolidate Duplicate Labels
<i>Fix & manage</i> has identified multiple labels with identical type, reference, label, origin, and spendable attributes within the same labelbase. As an efficiency measure, we have automatically merged XX of these duplicate labels, reducing them to YY unique labels."
</div>
</div>
<div class="alert alert-dismissible bd-callout bd-callout-info">
<div style="padding-right: 1.8rem">
<strong>{{ fix_suggestions }} suggestions available!</strong>
<p>
<i>Automatically Consolidate Duplicate Labels</i>
</p>
<p>
<em>Fix & Manage</em> has identified multiple labels with identical type, reference, label, origin, and spendable attributes within the same labelbase. As an efficiency measure, we have automatically merged {{ duplicate_labels_count }} of these duplicate labels, reducing them to {{ unique_labels_count }} unique labels.
</p>
</div>
</div>
-->
{% comment %}
{% if resulting_duplicates_all_identical_current_record_count %}
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title">Merge duplicate with identical attributes</h5>
</div>
<div class="card-body">
<p class="card-text">
Multiple labels with identical <tt>type</tt>, <tt>ref</tt>, <tt>label</tt>, <tt>origin</tt> and <tt>spendable</tt> attributes exists within the same labelbase.
</p>
<p>
Automatically merge {{ resulting_duplicates_all_identical_current_record_count }} labels into {{ resulting_duplicates_all_identical_final_record_count }} unique labels.
</p>
<tt>TODO: Show hint for auto cleanup (profile settings).</tt>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<a href="{% url "labelbase_merge" labelbase_id=labelbase.id %}?type={{ problem.type }}&ref={{ problem.ref }}&label={{ problem.label }}" class="btn btn-outline-primary">Merge</a>
</div>
</div>
</div>
{% endif %}
{% endcomment %}
{% comment %}
{% for problem in all_identical_records %}
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title"> all_identical_records Merge duplicate with identical type, ref and label</h5>
</div>
<div class="card-body">
<p class="card-text">
Multiple labels with the same type <tt>'{{ problem.type }}'</tt>, ref <tt>'{{ problem.ref }}'</tt> and label <tt>'{{ problem.label }}'</tt> exist within the same labelbase.
</p>
<br>
<tt>TODO: Depending on the attrs it's totally safe to automerge these recods.</tt>
<br>
<tt>TODO: Group those instead of listening one after the other, eg. 109 indentical records.</tt>
<br>
<tt>TODO: Show hint for auto cleanup (profile settings).</tt>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<a href="">Review & Merge</a> (<- this works in some cases , eg. addr,ref,label)
<a href="{% url "labelbase_merge" labelbase_id=labelbase.id %}?type={{ problem.type }}&ref={{ problem.ref }}&label={{ problem.label }}" class="btn btn-outline-primary">Review & Merge</a>
</div>
</div>
</div>
{% endfor %}
{% endcomment %}
{% comment %}
{% for problem in resulting_duplicates_type_and_ref_and_label %}
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title">Merge duplicate with identical type, ref and label</h5>
</div>
<div class="card-body">
<p class="card-text">
Multiple labels with the same type <tt>'{{ problem.type }}'</tt>, ref <tt>'{{ problem.ref }}'</tt> and label <tt>'{{ problem.label }}'</tt> exist within the same labelbase.
</p>
<br>
<tt>TODO: Depending on the attrs it's totally safe to automerge these recods.</tt>
<br>
<tt>TODO: Group those instead of listening one after the other, eg. 109 indentical records.</tt>
<br>
<tt>TODO: Show hint for auto cleanup (profile settings).</tt>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<a href="">Review & Merge</a> (<- this works in some cases , eg. addr,ref,label)
<a href="{% url "labelbase_merge" labelbase_id=labelbase.id %}?type={{ problem.type }}&ref={{ problem.ref }}&label={{ problem.label }}" class="btn btn-outline-primary">Review & Merge</a>
</div>
</div>
</div>
{% endfor %}
{% endcomment %}
{% comment %}
{% for problem in resulting_duplicates_type_and_ref %}
<div class="card mt-3">
<div class="card-header">
<h5 class="card-title">Merge duplicate{# .. with different attributes" #}{# vs "Merge duplicate with identical attributes" #}</h5>
</div>
<div class="card-body">
<p class="card-text">
Multiple labels with the same type <tt>'{{ problem.type }}'</tt> and ref <tt>'{{ problem.ref }}'</tt> exist within the same labelbase.
</p>
</div>
<div class="card-footer">
<div class="d-flex justify-content-end">
<a href="{% url "labelbase_merge" labelbase_id=labelbase.id %}?type={{ problem.type }}&ref={{ problem.ref }}" class="btn btn-outline-primary">Review & Merge</a>
</div>
</div>
</div>
{% endfor %}
{% endcomment %}
{% if fix_suggestions %}
{% else %}
Well done! Nothing to complain.
{% endif %}
{% comment %}
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse1">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>Merge duplicate with identical attributes</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
</div>
<div id="collapse1" class="collapse">
<div class="card-body">
{{ problem.type }} / {{ problem.ref }}
<p>You have duplicated labels in your system. Please review and remove duplicates.</p>
</div>
</div>
</div>
{% endcomment %}
{% comment %}
<!-- Problem Card 1 - Collapsed -->
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse1">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>Duplicated Labels</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
</div>
<div id="collapse1" class="collapse">
<div class="card-body">
<p>You have duplicated labels in your system. Please review and remove duplicates.</p>
</div>
</div>
</div>
{% endcomment %}
{% if resulting_empty_label_records %}
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse_empty_labels">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>{{ resulting_empty_label_records|length }} Empty Labels</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
<p>There are empty label texts in your labelbase. Add a label text to these labels for better organization.</p>
</div>
<div id="collapse_empty_labels" class="collapse">
<div class="card-body">
<ul>
{% for problem in resulting_empty_label_records %}
<li>
Record with type <tt>'{{ problem.type }}'</tt> and <br>
ref <tt>'{{ problem.ref }}'</tt> has an empty label text.
<span class="d-flex justify-content-end" style="margin-top: -2rem;">
<a href="{% url "edit_label" problem.id %}"
class="btn btn-outline-primary">Edit Label</a>
</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% if resulting_too_long_label_records %}
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse_too_long_labels">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>{{ resulting_too_long_label_records|length }} Labels are too long</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
<p>There are too long label texts in your labelbase.</p>
</div>
<div id="collapse_too_long_labels" class="collapse">
<div class="card-body">
<p><strong>BIP-329 suggests:</strong> Importing wallet may ignore records it does not store, and truncate labels if necessary.
A suggested default for maximum label length is 255 characters, and an importing wallet should consider warning the user if truncation is applied.
</p>
<ul>
{% for problem in resulting_too_long_label_records %}
<li>
Record with type <tt>'{{ problem.type }}'</tt> and <br>
ref <tt>'{{ problem.ref }}'</tt> has a too long label text.
<span class="d-flex justify-content-end" style="margin-top: -2rem;">
<a href="{% url "edit_label" problem.id %}"
class="btn btn-outline-primary">Edit Label</a>
</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
{% comment %}
<!-- Problem Card 2 - Collapsed -->
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse_too_long_labels">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>Label too long</span>
</h5>
</div>
<div id="collapse_too_long_labels" class="collapse">
<div class="card-body">
<p><!--Some labels exceed the maximum length of 255 characters.-->
This label exceeds the maximum length of 255 characters for this record.
</p>
<button class="btn btn-primary">Fix Now (label edit page)</button>
</div>
</div>
</div>
{% endcomment %}
{% comment %}
<!-- Problem Card 3 - Collapsed -->
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse3">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>Incorrect Labels</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
</div>
<div id="collapse3" class="collapse">
<div class="card-body">
<p>Some labels have incorrect information. Please update them for accuracy.</p>
</div>
</div>
</div>
{% endcomment %}
{% comment %}
<!-- Problem Card 4 - Collapsed -->
<div class="card mt-3">
<div class="card-header" data-bs-toggle="collapse" data-bs-target="#collapse4">
<h5 class="mb-0 d-flex justify-content-between align-items-center">
<span>Missing Labels</span>
<button class="btn btn-primary">Fix Now</button>
</h5>
</div>
<div id="collapse4" class="collapse">
<div class="card-body">
<p>Some labels are missing in your addresses derived from the XPUB. Add them for better organization.</p>
</div>
</div>
</div>
{% endcomment %}
{% else %} {# fix_suggestions #}
All good. Great job!
{% endif %} {# no fix_suggestions #}
</div>
<script>
{% addtoblock "js" %}
{% endaddtoblock %}
</script>
{% endblock %}