From bf11b675e82b4113c67500948fba441e92826c88 Mon Sep 17 00:00:00 2001 From: Junwei Zhao Date: Wed, 1 Apr 2026 21:52:29 +1100 Subject: [PATCH] Update ui --- .../migrations/0006_banned_ip_unbanned_at.py | 18 +++++++++++++ nginxmon/models.py | 5 ++++ nginxmon/templates/nginxmon/dashboard.html | 27 ++++++++++++++----- nginxmon/views.py | 8 +++--- 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 nginxmon/migrations/0006_banned_ip_unbanned_at.py diff --git a/nginxmon/migrations/0006_banned_ip_unbanned_at.py b/nginxmon/migrations/0006_banned_ip_unbanned_at.py new file mode 100644 index 0000000..8903e81 --- /dev/null +++ b/nginxmon/migrations/0006_banned_ip_unbanned_at.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.12 on 2026-04-01 10:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nginxmon', '0005_banned_ip_model'), + ] + + operations = [ + migrations.AddField( + model_name='bannedip', + name='unbanned_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/nginxmon/models.py b/nginxmon/models.py index 401dd3c..530b045 100644 --- a/nginxmon/models.py +++ b/nginxmon/models.py @@ -209,11 +209,16 @@ class BannedIP(models.Model): reason = models.TextField() request_count = models.IntegerField(default=0) banned_at = models.DateTimeField(auto_now_add=True) + unbanned_at = models.DateTimeField(null=True, blank=True) country = models.CharField(max_length=100, blank=True) city = models.CharField(max_length=100, blank=True) class Meta: ordering = ['-banned_at'] + @property + def is_unbanned(self): + return self.unbanned_at is not None + def __str__(self): return f'{self.ip} [{self.get_ban_source_display()}] @ {self.banned_at:%Y-%m-%d %H:%M}' diff --git a/nginxmon/templates/nginxmon/dashboard.html b/nginxmon/templates/nginxmon/dashboard.html index 44fc8b9..445e165 100644 --- a/nginxmon/templates/nginxmon/dashboard.html +++ b/nginxmon/templates/nginxmon/dashboard.html @@ -286,11 +286,12 @@ {% for ban in banned_ips %} - - - + + + - + {% if ban.city or ban.country %}{{ ban.city }}{% if ban.city and ban.country %}, {% endif %}{{ ban.country }}{% else %}—{% endif %} @@ -302,16 +303,28 @@ {{ ban.get_ban_source_display }} - {{ ban.reason }} - {{ ban.request_count }} - {{ ban.banned_at|date:"m/d H:i" }} + {{ ban.reason }} + {{ ban.request_count }} + + {{ ban.banned_at|date:"m/d H:i" }} + {% if ban.is_unbanned %} +
✓ unbanned {{ ban.unbanned_at|date:"m/d H:i" }} + {% endif %} + + {% if ban.is_unbanned %} + + Unbanned + + {% else %}
{% csrf_token %}
+ {% endif %} {% endfor %} diff --git a/nginxmon/views.py b/nginxmon/views.py index 24139f1..ef3e5f3 100644 --- a/nginxmon/views.py +++ b/nginxmon/views.py @@ -305,7 +305,8 @@ class DismissAlertView(View): class UnbanIPView(View): - """Remove an IP from both the BannedIP table and the ConfigMap block list.""" + """Mark an IP as manually unbanned — removes it from the ConfigMap block list + but keeps the BannedIP record so auto-ban never re-bans it.""" def post(self, request, pk): ban = get_object_or_404(BannedIP, pk=pk) @@ -318,8 +319,9 @@ class UnbanIPView(View): _write_blocked_ips(blocked) except Exception as exc: logger.warning('nginxmon unban: could not update ConfigMap for %s: %s', ip, exc) - ban.delete() - # Remove notes from log rows for this IP + ban.unbanned_at = timezone.now() + ban.save(update_fields=['unbanned_at']) + # Clear ban notes from log rows NginxAccessLog.objects.filter(remote_addr=ip).update(note='') messages.success(request, f'{ip} has been unbanned.') return redirect('nginxmon-dashboard')