|
@@ -3,7 +3,7 @@ from django.db import models
|
|
|
from django.db.models import Q
|
|
|
from django.forms import ModelForm, BaseInlineFormSet
|
|
|
from django.utils import timezone
|
|
|
-from django.urls import reverse
|
|
|
+from django.urls import reverse, path
|
|
|
from django.utils.html import format_html
|
|
|
from django.conf.urls import url
|
|
|
from django.template.response import TemplateResponse
|
|
@@ -26,11 +26,11 @@ from datetime import timedelta
|
|
|
from djadhere.utils import get_active_filter
|
|
|
from adhesions.models import Adhesion
|
|
|
from banking.models import PaymentUpdate
|
|
|
-from .models import Service, ServiceType, IPPrefix, IPResource, Route, Tunnel, \
|
|
|
+from .models import Service, ServiceType, IPPrefix, IPResource, IPResourceState, \
|
|
|
ServiceAllocation, Antenna, AntennaAllocation, Allocation, \
|
|
|
- Switch, Port
|
|
|
+ Route, Tunnel, Switch, Port
|
|
|
from .utils.notifications import notify_allocation
|
|
|
-from .forms import AntennaForm
|
|
|
+from .forms import AntennaForm, StopAllocationForm
|
|
|
|
|
|
|
|
|
### Filters
|
|
@@ -67,13 +67,15 @@ class ResourcePingFilter(admin.SimpleListFilter):
|
|
|
|
|
|
def queryset(self, request, queryset):
|
|
|
if self.value() == 'up':
|
|
|
- return queryset.filter(up=True)
|
|
|
+ return queryset.filter(last_state__state=IPResourceState.STATE_UP)
|
|
|
if self.value() == 'down':
|
|
|
- return queryset.filter(models.Q(up__isnull=True) | models.Q(up=False))
|
|
|
+ return queryset.exclude(last_state__state=IPResourceState.STATE_UP) # DOWN + UNKNOWN
|
|
|
if self.value() == 'down-since':
|
|
|
- return queryset.filter(up=False)
|
|
|
+ queryset = queryset.exclude(last_state__state=IPResourceState.STATE_UP)
|
|
|
+ return queryset.filter(last_time_up__isnull=False)
|
|
|
if self.value() == 'never-up':
|
|
|
- return queryset.filter(up__isnull=True)
|
|
|
+ queryset = queryset.exclude(last_state__state=IPResourceState.STATE_UP) # DOWN + UNKWON
|
|
|
+ return queryset.filter(last_time_up__isnull=True)
|
|
|
|
|
|
|
|
|
class ActiveServiceFilter(admin.SimpleListFilter):
|
|
@@ -204,14 +206,17 @@ class AllocationInline(admin.TabularInline):
|
|
|
return False
|
|
|
|
|
|
|
|
|
+class NewAllocationMixin:
|
|
|
+ verbose_name_plural = 'Nouvelle allocation'
|
|
|
+ max_num = 1
|
|
|
+
|
|
|
+ def get_queryset(self, request):
|
|
|
+ return super().get_queryset(request).model.objects.none()
|
|
|
+
|
|
|
+
|
|
|
class ActiveAllocationMixin:
|
|
|
verbose_name_plural = 'Allocations actives'
|
|
|
-
|
|
|
- def get_max_num(self, request, obj=None, **kwargs):
|
|
|
- existing = obj.allocations.count() if obj else 0
|
|
|
- # pour simplifier la validation, on ajoute qu’une allocation à la fois
|
|
|
- # il faudrait surcharger la méthode clean du formset pour supprimer cette limite
|
|
|
- return existing + 1
|
|
|
+ max_num = 0
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
return super().get_queryset(request).filter(get_active_filter())
|
|
@@ -237,32 +242,56 @@ class ServiceAllocationMixin:
|
|
|
return qs
|
|
|
|
|
|
|
|
|
-class AntennaAllocationMixin:
|
|
|
- model = AntennaAllocation
|
|
|
- fields = ('id', 'antenna', 'resource', 'start', 'end')
|
|
|
- raw_id_fields = ('resource',)
|
|
|
- autocomplete_fields = ('antenna',)
|
|
|
+#class AntennaAllocationMixin:
|
|
|
+# model = AntennaAllocation
|
|
|
+# fields = ('id', 'antenna', 'resource', 'start', 'end')
|
|
|
+# raw_id_fields = ('resource',)
|
|
|
+# autocomplete_fields = ('antenna',)
|
|
|
+#
|
|
|
+# def get_queryset(self, request):
|
|
|
+# qs = super().get_queryset(request)
|
|
|
+# qs = qs.select_related('antenna')
|
|
|
+# return qs
|
|
|
|
|
|
- def get_queryset(self, request):
|
|
|
- qs = super().get_queryset(request)
|
|
|
- qs = qs.select_related('antenna')
|
|
|
- return qs
|
|
|
+
|
|
|
+class NewServiceAllocationInline(ServiceAllocationMixin, NewAllocationMixin, AllocationInline):
|
|
|
+ fields = ('id', 'service', 'resource', 'route',)
|
|
|
|
|
|
|
|
|
class ActiveServiceAllocationInline(ServiceAllocationMixin, ActiveAllocationMixin, AllocationInline):
|
|
|
- pass
|
|
|
+ fields = ('id', 'service', 'resource', 'route', 'start', 'stop',)
|
|
|
+ readonly_fields = ('service', 'start', 'resource', 'stop',)
|
|
|
+
|
|
|
+ def stop(self, obj):
|
|
|
+ return format_html('<a href="{}" class="deletelink">Terminer</a>', reverse('admin:stop-allocation', kwargs={'resource': obj.resource.ip}))
|
|
|
+ stop.short_description = 'Terminer l’allocation'
|
|
|
|
|
|
|
|
|
class InactiveServiceAllocationInline(ServiceAllocationMixin, InactiveAllocationMixin, AllocationInline):
|
|
|
- pass
|
|
|
+ fields = ('id', 'service', 'resource', 'route', 'start', 'end')
|
|
|
+ readonly_fields = ('service', 'resource', 'route', 'start', 'end')
|
|
|
|
|
|
|
|
|
-class ActiveAntennaAllocationInline(AntennaAllocationMixin, ActiveAllocationMixin, AllocationInline):
|
|
|
- pass
|
|
|
+#class ActiveAntennaAllocationInline(AntennaAllocationMixin, ActiveAllocationMixin, AllocationInline):
|
|
|
+# pass
|
|
|
|
|
|
|
|
|
-class InactiveAntennaAllocationInline(AntennaAllocationMixin, InactiveAllocationMixin, AllocationInline):
|
|
|
- pass
|
|
|
+#class InactiveAntennaAllocationInline(AntennaAllocationMixin, InactiveAllocationMixin, AllocationInline):
|
|
|
+# pass
|
|
|
+
|
|
|
+
|
|
|
+class IPResourceStateInline(admin.TabularInline):
|
|
|
+ model = IPResourceState
|
|
|
+ verbose_name_plural = 'Historique des derniers changements d’état'
|
|
|
+ fields = ['date']
|
|
|
+ readonly_fields = ['date']
|
|
|
+ ordering = ['-date']
|
|
|
+
|
|
|
+ def has_add_permission(self, request):
|
|
|
+ return False
|
|
|
+
|
|
|
+ def has_delete_permission(self, request, obj=None):
|
|
|
+ return False
|
|
|
|
|
|
|
|
|
class PortInline(admin.TabularInline):
|
|
@@ -292,15 +321,6 @@ class ServicePortInline(PortInline):
|
|
|
readonly_fields = ('switch', 'port', 'up',)
|
|
|
|
|
|
|
|
|
-### Actions
|
|
|
-
|
|
|
-def ends_resource(resource, request, queryset):
|
|
|
- now = timezone.now()
|
|
|
- queryset.exclude(start__lte=now, end__isnull=False).update(end=now)
|
|
|
- # TODO: send mail
|
|
|
-ends_resource.short_description = 'Terminer les allocations sélectionnées'
|
|
|
-
|
|
|
-
|
|
|
### ModelAdmin
|
|
|
|
|
|
class ServiceAdmin(admin.ModelAdmin):
|
|
@@ -335,7 +355,11 @@ class ServiceAdmin(admin.ModelAdmin):
|
|
|
inlines = []
|
|
|
if obj and obj.ports.exists():
|
|
|
inlines += [ServicePortInline]
|
|
|
- inlines += [ActiveServiceAllocationInline, InactiveServiceAllocationInline]
|
|
|
+ inlines += [NewServiceAllocationInline]
|
|
|
+ if obj and obj.active_allocations.exists():
|
|
|
+ inlines += [ActiveServiceAllocationInline]
|
|
|
+ if obj and obj.inactive_allocations.exists():
|
|
|
+ inlines += [InactiveServiceAllocationInline]
|
|
|
return [inline(self.model, self.admin_site) for inline in inlines]
|
|
|
|
|
|
def get_actions(self, request):
|
|
@@ -385,35 +409,41 @@ class IPResourceAdmin(admin.ModelAdmin):
|
|
|
)
|
|
|
search_fields = ('=ip', 'notes',)
|
|
|
actions = ['contact_ip_owners']
|
|
|
+ ordering = ['ip']
|
|
|
+ inlines = [ IPResourceStateInline ]
|
|
|
|
|
|
def get_fields(self, request, obj=None):
|
|
|
return self.get_readonly_fields(request, obj)
|
|
|
|
|
|
def get_readonly_fields(self, request, obj=None):
|
|
|
fields = ['ip']
|
|
|
- if obj and obj.reserved:
|
|
|
- fields += ['reserved']
|
|
|
- if obj and not obj.in_use:
|
|
|
- fields += ['last_use']
|
|
|
- if obj and obj.last_time_up and obj.last_check:
|
|
|
- fields += ['last_time_up', 'last_check']
|
|
|
- if obj.category == IPResource.CATEGORY_PUBLIC:
|
|
|
- fields += ['password']
|
|
|
- if obj and obj.checkmk_label:
|
|
|
- fields += ['checkmk']
|
|
|
- if obj and obj.notes:
|
|
|
- fields += ['notes']
|
|
|
+ if obj:
|
|
|
+ if obj.reserved:
|
|
|
+ fields += ['reserved']
|
|
|
+ if not obj.in_use:
|
|
|
+ fields += ['last_use']
|
|
|
+ fields += ['last_state']
|
|
|
+ if obj.last_state.state != IPResourceState.STATE_UP:
|
|
|
+ fields += ['last_time_up']
|
|
|
+ if obj.category == IPResource.CATEGORY_PUBLIC:
|
|
|
+ fields += ['password']
|
|
|
+ if obj.checkmk_label:
|
|
|
+ fields += ['checkmk']
|
|
|
+ if obj.notes:
|
|
|
+ fields += ['notes']
|
|
|
return fields
|
|
|
|
|
|
def get_inline_instances(self, request, obj=None):
|
|
|
- if obj:
|
|
|
- if obj.category == 0:
|
|
|
- inlines = (ActiveServiceAllocationInline, InactiveServiceAllocationInline,)
|
|
|
- elif obj.category == 1:
|
|
|
- inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
|
|
|
- else:
|
|
|
- inlines = ()
|
|
|
- return [inline(self.model, self.admin_site) for inline in inlines]
|
|
|
+ super_inlines = super().get_inline_instances(request, obj)
|
|
|
+ inlines = []
|
|
|
+ if obj and obj.category == IPResource.CATEGORY_PUBLIC:
|
|
|
+ if obj.allocations.filter(get_active_filter()).exists():
|
|
|
+ inlines += [ActiveServiceAllocationInline]
|
|
|
+ else:
|
|
|
+ inlines += [NewServiceAllocationInline]
|
|
|
+ if obj.allocations.exclude(get_active_filter()).exists():
|
|
|
+ inlines += [InactiveServiceAllocationInline]
|
|
|
+ return [inline(self.model, self.admin_site) for inline in inlines] + super_inlines
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
qs = super().get_queryset(request)
|
|
@@ -426,13 +456,12 @@ class IPResourceAdmin(admin.ModelAdmin):
|
|
|
default=None,
|
|
|
))
|
|
|
qs = qs.annotate(
|
|
|
- up=models.Case(
|
|
|
- models.When(last_check__isnull=False, last_time_up__isnull=False, last_time_up=models.F('last_check'), then=True),
|
|
|
- models.When(last_check__isnull=False, last_time_up__isnull=False, then=False),
|
|
|
+ downtime=models.Case(
|
|
|
+ models.When(last_state__state=IPResourceState.STATE_UP, then=models.F('last_state__date')-models.Value(now)),
|
|
|
+ models.When(last_state__state=IPResourceState.STATE_DOWN, then=models.Value(now)-models.F('last_time_up')),
|
|
|
default=None,
|
|
|
- output_field=models.NullBooleanField(),
|
|
|
+ output_field=models.DurationField(),
|
|
|
))
|
|
|
- qs = qs.annotate(downtime=models.F('last_check') - models.F('last_time_up'))
|
|
|
qs = qs.annotate(
|
|
|
route=models.Case(
|
|
|
models.When(
|
|
@@ -461,17 +490,19 @@ class IPResourceAdmin(admin.ModelAdmin):
|
|
|
last_use.admin_order_field = 'last_use'
|
|
|
|
|
|
def ping(self, obj):
|
|
|
- if obj.up:
|
|
|
+ if obj.last_state.state == IPResourceState.STATE_UP:
|
|
|
label = 'UP'
|
|
|
- elif obj.last_time_up:
|
|
|
- label = 'dernier ping : ' + naturaltime(obj.last_time_up)
|
|
|
else:
|
|
|
- label = 'DOWN'
|
|
|
+ if obj.last_time_up:
|
|
|
+ label = 'dernier ping : ' + naturaltime(obj.last_time_up)
|
|
|
+ else:
|
|
|
+ label = 'DOWN'
|
|
|
if obj.checkmk_url:
|
|
|
return format_html('<a href="{}">{}</a>', obj.checkmk_url, label)
|
|
|
else:
|
|
|
return label
|
|
|
ping.short_description = 'ping'
|
|
|
+ #ping.admin_order_field = 'last_state__date'
|
|
|
ping.admin_order_field = 'downtime'
|
|
|
|
|
|
def route(self, obj):
|
|
@@ -495,6 +526,35 @@ class IPResourceAdmin(admin.ModelAdmin):
|
|
|
return HttpResponseRedirect(reverse('admin:contact-adherents') + "?pk=%s" % pk)
|
|
|
contact_ip_owners.short_description = 'Contacter les adhérents'
|
|
|
|
|
|
+ def stop_allocation(self, request, resource):
|
|
|
+ resource = self.get_object(request, resource)
|
|
|
+ allocation = resource.allocations.filter(get_active_filter()).first()
|
|
|
+ if not allocation: # L’IP n’est pas allouée
|
|
|
+ return HttpResponseRedirect(reverse('admin:services_ipresource_change', args=[resource.pk]))
|
|
|
+ form = StopAllocationForm(request.POST or None)
|
|
|
+ if request.method == 'POST' and form.is_valid():
|
|
|
+ self.message_user(request, 'Allocation stoppée.')
|
|
|
+ allocation.end = timezone.now()
|
|
|
+ allocation.save()
|
|
|
+ notify_allocation(request, allocation)
|
|
|
+ # Il faudrait rajouter un redirect dans l’URL pour rediriger vers l’IP ou le Service
|
|
|
+ return HttpResponseRedirect(reverse('admin:services_ipresource_change', args=[resource.pk]))
|
|
|
+ context = self.admin_site.each_context(request)
|
|
|
+ context.update({
|
|
|
+ 'opts': self.model._meta,
|
|
|
+ 'title': 'Stopper une allocation',
|
|
|
+ 'object': resource,
|
|
|
+ 'media': self.media,
|
|
|
+ 'form': form,
|
|
|
+ })
|
|
|
+ return TemplateResponse(request, "admin/services/ipresource/stop_allocation.html", context)
|
|
|
+
|
|
|
+ def get_urls(self):
|
|
|
+ my_urls = [
|
|
|
+ path('<resource>/stop/', self.admin_site.admin_view(self.stop_allocation), name='stop-allocation'),
|
|
|
+ ]
|
|
|
+ return my_urls + super().get_urls()
|
|
|
+
|
|
|
def get_actions(self, request):
|
|
|
actions = super().get_actions(request)
|
|
|
if 'delete_selected' in actions:
|
|
@@ -508,6 +568,25 @@ class IPResourceAdmin(admin.ModelAdmin):
|
|
|
return False
|
|
|
|
|
|
|
|
|
+class IPResourceStateAdmin(admin.ModelAdmin):
|
|
|
+ list_display = ('ip', 'date', 'state',)
|
|
|
+
|
|
|
+ def get_actions(self, request):
|
|
|
+ actions = super().get_actions(request)
|
|
|
+ if 'delete_selected' in actions:
|
|
|
+ del actions['delete_selected']
|
|
|
+ return actions
|
|
|
+
|
|
|
+ def has_add_permission(self, request, obj=None):
|
|
|
+ return False
|
|
|
+
|
|
|
+ def has_change_permission(self, request, obj=None):
|
|
|
+ return False
|
|
|
+
|
|
|
+ def has_delete_permission(self, request, obj=None):
|
|
|
+ return False
|
|
|
+
|
|
|
+
|
|
|
class RouteAdmin(admin.ModelAdmin):
|
|
|
list_display = ('name',)
|
|
|
search_fields = ('name',)
|
|
@@ -590,7 +669,7 @@ class ActiveAntennaLayer(GeoJSONLayerView):
|
|
|
|
|
|
|
|
|
class AntennaAdmin(admin.ModelAdmin):
|
|
|
- inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
|
|
|
+ #inlines = (ActiveAntennaAllocationInline, InactiveAntennaAllocationInline,)
|
|
|
list_filter = (
|
|
|
AntennaPrefixFilter,
|
|
|
AntennaPositionFilter,
|