logging attckers try and add location to admin
This commit is contained in:
@@ -10,6 +10,12 @@ from django.contrib.auth.models import Group
|
|||||||
from unfold.forms import AdminPasswordChangeForm
|
from unfold.forms import AdminPasswordChangeForm
|
||||||
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
|
from unfold.forms import AdminPasswordChangeForm, UserChangeForm, UserCreationForm
|
||||||
from utils.admin import ModelAdmin
|
from utils.admin import ModelAdmin
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
from folium import Map, Marker
|
||||||
|
from unfold.decorators import action, display
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
|
||||||
class UserAddressInLine(TabularInline):
|
class UserAddressInLine(TabularInline):
|
||||||
model = UserAddressModel
|
model = UserAddressModel
|
||||||
extra = 0
|
extra = 0
|
||||||
@@ -96,3 +102,36 @@ class PushSubscription(ModelAdmin, ImportExportModelAdmin):
|
|||||||
export_form_class = ExportForm
|
export_form_class = ExportForm
|
||||||
compressed_fields = True
|
compressed_fields = True
|
||||||
warn_unsaved_form = True
|
warn_unsaved_form = True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(SecurityBreachAttemptModel)
|
||||||
|
class SecurityBreachAttemptAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
|
import_form_class = ImportForm
|
||||||
|
export_form_class = ExportForm
|
||||||
|
compressed_fields = True
|
||||||
|
warn_unsaved_form = True
|
||||||
|
change_form_template = 'loction_chagne_form.html'
|
||||||
|
list_display = ['ip', 'country', 'region_name', 'city', 'zip_code', 'isp', 'created_at', 'trys', 'display_viewd']
|
||||||
|
|
||||||
|
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||||
|
extra_context = extra_context or {}
|
||||||
|
obj = self.get_object(request, object_id)
|
||||||
|
|
||||||
|
if obj and obj.lat and obj.lon:
|
||||||
|
m = Map(location=[obj.lat, obj.lon], zoom_start=10)
|
||||||
|
Marker([obj.lat, obj.lon], popup=f"Location: {obj.ip}").add_to(m)
|
||||||
|
map_html = m._repr_html_()
|
||||||
|
extra_context['map_html'] = map_html
|
||||||
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
@display(description='دیده شده')
|
||||||
|
def display_viewd(self, instance):
|
||||||
|
if instance.viewd:
|
||||||
|
svg = f'<a href="/accounts/attack/view/{instance.id}"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><path fill="green" d="M12 16q1.875 0 3.188-1.312T16.5 11.5t-1.312-3.187T12 7T8.813 8.313T7.5 11.5t1.313 3.188T12 16m0-1.8q-1.125 0-1.912-.788T9.3 11.5t.788-1.912T12 8.8t1.913.788t.787 1.912t-.787 1.913T12 14.2m0 4.8q-3.65 0-6.65-2.037T1 11.5q1.35-3.425 4.35-5.462T12 4t6.65 2.038T23 11.5q-1.35 3.425-4.35 5.463T12 19m0-2q2.825 0 5.188-1.487T20.8 11.5q-1.25-2.525-3.613-4.012T12 6T6.813 7.488T3.2 11.5q1.25 2.525 3.613 4.013T12 17"/></svg></a>'
|
||||||
|
else:
|
||||||
|
svg = f'<a href="/accounts/attack/view/{instance.id}"><svg xmlns="http://www.w3.org/2000/svg" width="25" height="25" viewBox="0 0 24 24"><path fill="#c30009" d="m19.1 21.9l-3.5-3.45q-.875.275-1.775.413T12 19q-3.35 0-6.125-1.8t-4.35-4.75q-.125-.225-.187-.462t-.063-.488t.063-.488t.187-.462q.55-.975 1.175-1.9T4.15 7L2.075 4.9Q1.8 4.625 1.8 4.213t.3-.713q.275-.275.7-.275t.7.275l17 17q.275.275.288.688t-.288.712q-.275.275-.7.275t-.7-.275M12 16q.275 0 .525-.025t.5-.1l-5.4-5.4q-.075.25-.1.5T7.5 11.5q0 1.875 1.313 3.188T12 16m0-12q3.35 0 6.138 1.813t4.362 4.762q.125.2.188.438t.062.487t-.05.488t-.175.437q-.475.925-1.062 1.75t-1.313 1.55q-.35.35-.825.325t-.825-.375l-2-2q-.175-.175-.225-.413t.025-.487q.1-.325.15-.625t.05-.65q0-1.875-1.312-3.187T12 7q-.35 0-.65.05t-.625.15q-.25.075-.5.025T9.8 7l-.825-.825q-.475-.475-.312-1.1t.787-.8q.625-.125 1.263-.2T12 4m1.975 5.65q.275.325.462.713t.238.812q.025.2-.15.275t-.325-.075l-2.05-2.05Q12 9.175 12.088 9t.287-.175q.475.05.875.263t.725.562"/></svg></a>'
|
||||||
|
|
||||||
|
return format_html(
|
||||||
|
svg
|
||||||
|
)
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-02-20 20:25
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0014_alter_pushsubscription_created_at_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SecurityBreachAttemptModel',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('ip', models.CharField(max_length=40, unique=True, verbose_name='آدرس آی\u200cپی')),
|
||||||
|
('country', models.CharField(blank=True, max_length=40, null=True, verbose_name='کشور')),
|
||||||
|
('region_name', models.CharField(blank=True, max_length=40, null=True, verbose_name='منطقه')),
|
||||||
|
('city', models.CharField(blank=True, max_length=40, null=True, verbose_name='شهر')),
|
||||||
|
('zip_code', models.CharField(blank=True, max_length=40, null=True, verbose_name='کد پستی')),
|
||||||
|
('lon', models.CharField(blank=True, max_length=40, null=True, verbose_name='طول جغرافیایی')),
|
||||||
|
('lat', models.CharField(blank=True, max_length=40, null=True, verbose_name='عرض جغرافیایی')),
|
||||||
|
('isp', models.CharField(blank=True, max_length=40, null=True, verbose_name='ارائه\u200cدهنده اینترنت (ISP)')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'تلاش نفوذ',
|
||||||
|
'verbose_name_plural': 'تلاش\u200cهای نفوذ',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-02-20 21:17
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0015_securitybreachattemptmodel'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='securitybreachattemptmodel',
|
||||||
|
name='viewd',
|
||||||
|
field=models.BooleanField(default=False, verbose_name='تماشا شده'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-02-20 21:35
|
||||||
|
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0016_securitybreachattemptmodel_viewd'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='securitybreachattemptmodel',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateField(auto_now_add=True, default=django.utils.timezone.now),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='securitybreachattemptmodel',
|
||||||
|
name='trys',
|
||||||
|
field=models.IntegerField(default=0),
|
||||||
|
),
|
||||||
|
]
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.1.2 on 2025-02-20 21:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('account', '0017_securitybreachattemptmodel_created_at_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='securitybreachattemptmodel',
|
||||||
|
name='created_at',
|
||||||
|
field=models.DateTimeField(auto_now_add=True, verbose_name='شروع حمله'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='securitybreachattemptmodel',
|
||||||
|
name='trys',
|
||||||
|
field=models.IntegerField(default=0, verbose_name='تعداد تلاش ها'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -199,3 +199,45 @@ class PushSubscription(models.Model):
|
|||||||
)
|
)
|
||||||
except WebPushException as ex:
|
except WebPushException as ex:
|
||||||
print(f"Failed to send notification to {sub.user}:", ex)
|
print(f"Failed to send notification to {sub.user}:", ex)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def get_location_from_ip(ip_address):
|
||||||
|
try:
|
||||||
|
response = requests.get(f"http://ip-api.com/json/{ip_address}")
|
||||||
|
data = response.json()
|
||||||
|
if data["status"] == "success":
|
||||||
|
return data['country'], data['regionName'], data['city'], data.get('zip', 'ناموجود'), data['lat'], data['lon'], data['isp']
|
||||||
|
else:
|
||||||
|
print("Error fetching data: ", data["message"])
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
class SecurityBreachAttemptModel(models.Model):
|
||||||
|
ip = models.CharField(max_length=40, unique=True, verbose_name="آدرس آیپی")
|
||||||
|
country = models.CharField(max_length=40, verbose_name="کشور", blank=True, null=True)
|
||||||
|
region_name = models.CharField(max_length=40, verbose_name="منطقه", blank=True, null=True)
|
||||||
|
city = models.CharField(max_length=40, verbose_name="شهر", blank=True, null=True)
|
||||||
|
zip_code = models.CharField(max_length=40, verbose_name="کد پستی", blank=True, null=True)
|
||||||
|
lon = models.CharField(max_length=40, verbose_name="طول جغرافیایی", blank=True, null=True)
|
||||||
|
lat = models.CharField(max_length=40, verbose_name="عرض جغرافیایی", blank=True, null=True)
|
||||||
|
isp = models.CharField(max_length=40, verbose_name="ارائهدهنده اینترنت (ISP)", blank=True, null=True)
|
||||||
|
viewd = models.BooleanField(default=False, verbose_name='تماشا شده')
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True, verbose_name='شروع حمله')
|
||||||
|
trys = models.IntegerField(default=0, verbose_name='تعداد تلاش ها')
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if not self.id:
|
||||||
|
location_data = get_location_from_ip(self.ip)
|
||||||
|
if location_data:
|
||||||
|
self.country, self.region_name, self.city, self.zip_code, self.lat, self.lon, self.isp = location_data
|
||||||
|
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'تلاش نفوذ از {self.ip} در {self.city}, {self.country}'
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "تلاش نفوذ"
|
||||||
|
verbose_name_plural = "تلاشهای نفوذ"
|
||||||
@@ -12,5 +12,6 @@ urlpatterns = [
|
|||||||
path('address/delete/<int:pk>', views.DeleteAddressView.as_view(), name='delete-address'),
|
path('address/delete/<int:pk>', views.DeleteAddressView.as_view(), name='delete-address'),
|
||||||
path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'),
|
path('address/list', views.GetUserAddressesView.as_view(), name='list-addresses'),
|
||||||
path('address/<int:pk>', views.GetIDUserAddressView.as_view(), name='get-ID-address'),
|
path('address/<int:pk>', views.GetIDUserAddressView.as_view(), name='get-ID-address'),
|
||||||
path('subscribe', views.SubscribeView.as_view(), name='subscibe')
|
path('subscribe', views.SubscribeView.as_view(), name='subscibe'),
|
||||||
|
path('attack/view/<int:pk>', views.ChangeViewAttack.as_view(), name='attack-view'),
|
||||||
]
|
]
|
||||||
@@ -3,13 +3,14 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework import generics, permissions, status
|
from rest_framework import generics, permissions, status
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from .serializers import *
|
from .serializers import *
|
||||||
from .models import UserAddressModel, User
|
from .models import UserAddressModel, User, SecurityBreachAttemptModel
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from rest_framework_simplejwt.tokens import RefreshToken
|
from rest_framework_simplejwt.tokens import RefreshToken
|
||||||
import ghasedak_sms
|
import ghasedak_sms
|
||||||
|
from django.views import View
|
||||||
# this works only need to be used
|
# this works only need to be used
|
||||||
# class APIView(APIView):
|
# class APIView(APIView):
|
||||||
# def __init__(self, *args, **kwargs):
|
# def __init__(self, *args, **kwargs):
|
||||||
@@ -186,3 +187,11 @@ class SubscribeView(APIView):
|
|||||||
)
|
)
|
||||||
return Response(status=status.HTTP_201_CREATED)
|
return Response(status=status.HTTP_201_CREATED)
|
||||||
return Response(status=status.HTTP_400_BAD_REQUEST)
|
return Response(status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
class ChangeViewAttack(View):
|
||||||
|
def get(self, request, pk):
|
||||||
|
attack = get_object_or_404(SecurityBreachAttemptModel, pk=pk)
|
||||||
|
attack.viewd = not attack.viewd
|
||||||
|
attack.save()
|
||||||
|
return redirect('admin:account_securitybreachattemptmodel_changelist')
|
||||||
@@ -209,6 +209,12 @@ UNFOLD = {
|
|||||||
"icon": "contact_mail",
|
"icon": "contact_mail",
|
||||||
"link": reverse_lazy("admin:account_useraddressmodel_changelist"),
|
"link": reverse_lazy("admin:account_useraddressmodel_changelist"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": _("تلاشهای نفوذ"),
|
||||||
|
"icon": "gpp_maybe",
|
||||||
|
"link": reverse_lazy("admin:account_securitybreachattemptmodel_changelist"),
|
||||||
|
"badge": 'utils.admin.new_attck_count'
|
||||||
|
},
|
||||||
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
+5
-10
@@ -9,7 +9,7 @@ from django.views.generic import RedirectView, TemplateView
|
|||||||
from unfold.views import UnfoldModelAdminViewMixin
|
from unfold.views import UnfoldModelAdminViewMixin
|
||||||
from order.models import OrderModel
|
from order.models import OrderModel
|
||||||
from ticket.models import Ticket
|
from ticket.models import Ticket
|
||||||
|
from account.models import SecurityBreachAttemptModel
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
@@ -267,20 +267,15 @@ class FakeAdminLoginView(View):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
# Log empty attempt (optional)
|
|
||||||
ip = request.META.get("REMOTE_ADDR")
|
ip = request.META.get("REMOTE_ADDR")
|
||||||
print(f"Honeypot page accessed from IP: {ip}")
|
hacker, created = SecurityBreachAttemptModel.objects.get_or_create(ip=ip)
|
||||||
|
|
||||||
return render(request, 'admin/fake_login.html', self.get_context(request))
|
return render(request, 'admin/fake_login.html', self.get_context(request))
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
username = request.POST.get("username")
|
|
||||||
password = request.POST.get("password") # Never actually used
|
|
||||||
ip = request.META.get("REMOTE_ADDR")
|
ip = request.META.get("REMOTE_ADDR")
|
||||||
|
hacker, created = SecurityBreachAttemptModel.objects.get_or_create(ip=ip)
|
||||||
print(f"Honeypot triggered! IP: {ip}, Username: {username}")
|
hacker.trys += 1
|
||||||
|
hacker.save()
|
||||||
messages.error(request, "Please correct the error below.")
|
messages.error(request, "Please correct the error below.")
|
||||||
messages.error(request, "Please enter the correct شماره تماس and password for a staff account. Note that both fields may be case-sensitive.")
|
messages.error(request, "Please enter the correct شماره تماس and password for a staff account. Note that both fields may be case-sensitive.")
|
||||||
|
|
||||||
# Redirect back to fake login page with context
|
|
||||||
return render(request, 'admin/fake_login.html', self.get_context(request))
|
return render(request, 'admin/fake_login.html', self.get_context(request))
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load unfold %}
|
||||||
|
{% load i18n admin_urls static admin_modify %}
|
||||||
|
|
||||||
|
{% block extrahead %}{{ block.super }}
|
||||||
|
<script src="{% url 'admin:jsi18n' %}"></script>
|
||||||
|
{{ media }}
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-form{% endblock %}
|
||||||
|
|
||||||
|
{% if not is_popup %}
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="px-4 lg:px-8">
|
||||||
|
<div class="container mb-6 mx-auto -my-3 lg:mb-12">
|
||||||
|
<ul class="flex flex-wrap">
|
||||||
|
{% url 'admin:index' as link %}
|
||||||
|
{% trans 'Home' as name %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=name %}
|
||||||
|
|
||||||
|
{% url 'admin:app_list' app_label=opts.app_label as link %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.app_config.verbose_name %}
|
||||||
|
|
||||||
|
{% if has_view_permission %}
|
||||||
|
{% url opts|admin_urlname:'changelist' as link %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link=link name=opts.verbose_name_plural|capfirst %}
|
||||||
|
{% else %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=opts.verbose_name_plural|capfirst %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if add %}
|
||||||
|
{% blocktranslate trimmed with name=opts.verbose_name asvar breadcrumb_name %}
|
||||||
|
Add {{ name }}
|
||||||
|
{% endblocktranslate %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=breadcrumb_name %}
|
||||||
|
{% else %}
|
||||||
|
{% include 'unfold/helpers/breadcrumb_item.html' with link='' name=original|truncatewords:'18' %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{% block nav-global-side %}
|
||||||
|
{% if has_add_permission %}
|
||||||
|
{% include "unfold/helpers/add_link.html" %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div id="content-main" class="flex flex-col gap-4">
|
||||||
|
{% block form_before %}{% endblock %}
|
||||||
|
{% if adminform.model_admin.change_form_outer_before_template %}
|
||||||
|
{% include adminform.model_admin.change_form_outer_before_template %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="border border-base-200 border-dashed mb-4 p-3 rounded dark:border-base-700 gap-1.5">
|
||||||
|
{% if original.lon and original.lat %}
|
||||||
|
<div class="location-details" style="margin-bottom: 0rem; padding: 0.5rem 1rem; background-color: rgba(var(--color-primary-100), 0.3); border-radius: var(--border-radius,6px);">
|
||||||
|
<div style="display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: center; align-items: center; font-size: 0.9em; color: rgb(var(--color-primary-700));" dir="rtl">
|
||||||
|
{% if original.country %}
|
||||||
|
<span class="badge" style="padding: 0.25rem 0.75rem; background-color: rgb(var(--color-primary-500)); color: white; border-radius: 20px;">
|
||||||
|
🇺🇳 {{ original.country }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if original.region_name %}
|
||||||
|
<span style="display: inline-flex; align-items: center;">
|
||||||
|
<i class="fas fa-mountain mr-1"></i>
|
||||||
|
{{ original.region_name }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if original.city %}
|
||||||
|
<span style="display: inline-flex; align-items: center;">
|
||||||
|
<i class="fas fa-city mr-1"></i>
|
||||||
|
{{ original.city }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if original.zip_code %}
|
||||||
|
<span style="display: inline-flex; align-items: center;">
|
||||||
|
<i class="fas fa-mail-bulk mr-1"></i>
|
||||||
|
{{ original.zip_code }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if original.isp %}
|
||||||
|
<span style="display: inline-flex; align-items: center;">
|
||||||
|
<i class="fas fa-network-wired mr-1"></i>
|
||||||
|
{{ original.isp }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
{% if original.ip %}
|
||||||
|
<span style="display: inline-flex; align-items: center;">
|
||||||
|
<i class="fas fa-network-wired mr-1"></i>
|
||||||
|
{{ original.ip }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if map_html %}
|
||||||
|
<!-- Render the Folium map with a fixed height -->
|
||||||
|
<div style="display: flex;justify-content: center;align-items: center;width: 100%;padding: 25px 0px;">
|
||||||
|
<div style="border-radius: var(--border-radius,6px);width: 100%;max-width: 800px;">
|
||||||
|
{{ map_html|safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<p>نقشه در دسترس نیست</p>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<p>موقعیت جغرافیایی موجود نیست</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!--
|
||||||
|
<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}{% if form_url %}action="{{ form_url }}" {% endif %}method="post" id="{{ opts.model_name }}_form" {% if adminform.model_admin.warn_unsaved_form %}class="warn-unsaved-form"{% endif %} novalidate>
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
{% if adminform.model_admin.change_form_before_template %}
|
||||||
|
{% include adminform.model_admin.change_form_before_template %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block form_top %}{% endblock %}
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% if is_popup %}
|
||||||
|
<input type="hidden" name="{{ is_popup_var }}" value="1">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if to_field %}
|
||||||
|
<input type="hidden" name="{{ to_field_var }}" value="{{ to_field }}">
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% include "unfold/helpers/messages/errornote.html" with errors=errors %}
|
||||||
|
{% include "unfold/helpers/messages/error.html" with errors=adminform.form.non_field_errors %}
|
||||||
|
|
||||||
|
{% block field_sets %}
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
{% if "tab" not in fieldset.classes %}
|
||||||
|
{% include 'admin/includes/fieldset.html' %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% include "unfold/helpers/fieldsets_tabs.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block after_field_sets %}{% endblock %}
|
||||||
|
|
||||||
|
{% block inline_field_sets %}
|
||||||
|
{% for inline_admin_formset in inline_admin_formsets %}
|
||||||
|
{% include inline_admin_formset.opts.template %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block after_related_objects %}{% endblock %}
|
||||||
|
|
||||||
|
{% if adminform.model_admin.change_form_after_template %}
|
||||||
|
{% include adminform.model_admin.change_form_after_template %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block submit_buttons_bottom %}{% submit_row %}{% endblock %}
|
||||||
|
|
||||||
|
{% block admin_change_form_document_ready %}
|
||||||
|
<script id="django-admin-form-add-constants" src="{% static 'admin/js/change_form.js' %}"{% if adminform and add %} data-model-name="{{ opts.model_name }}"{% endif %} async></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% prepopulated_fields_js %}
|
||||||
|
</div>
|
||||||
|
</form> -->
|
||||||
|
{% if adminform.model_admin.change_form_outer_after_template %}
|
||||||
|
{% include adminform.model_admin.change_form_outer_after_template %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block form_after %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajs %}
|
||||||
|
<!-- Add Leaflet JS for Folium -->
|
||||||
|
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
|
||||||
|
{% endblock %}
|
||||||
@@ -2,6 +2,7 @@ from order.models import OrderModel
|
|||||||
from product.models import DollorModel, CommentModel
|
from product.models import DollorModel, CommentModel
|
||||||
from ticket.models import Ticket
|
from ticket.models import Ticket
|
||||||
from home.models import LearnVideoModel
|
from home.models import LearnVideoModel
|
||||||
|
from account.models import SecurityBreachAttemptModel
|
||||||
|
|
||||||
def admin_pending_count(request):
|
def admin_pending_count(request):
|
||||||
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
|
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
|
||||||
@@ -20,6 +21,8 @@ def new_ticket_count(request):
|
|||||||
def new_learn_video_count(request):
|
def new_learn_video_count(request):
|
||||||
return LearnVideoModel.objects.filter(viewd=False).count()
|
return LearnVideoModel.objects.filter(viewd=False).count()
|
||||||
|
|
||||||
|
def new_attck_count(request):
|
||||||
|
return SecurityBreachAttemptModel.objects.filter(viewd=False).count()
|
||||||
|
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from unfold.admin import ModelAdmin
|
from unfold.admin import ModelAdmin
|
||||||
|
|||||||
Reference in New Issue
Block a user