diff --git a/machines/admin.py b/machines/admin.py index fa6262ac..8c0ef947 100644 --- a/machines/admin.py +++ b/machines/admin.py @@ -23,7 +23,7 @@ from django.contrib import admin from reversion.admin import VersionAdmin -from .models import IpType, Machine, MachineType, Domain, IpList, Interface, Extension, Mx, Ns +from .models import IpType, Machine, MachineType, Domain, IpList, Interface, Extension, Mx, Ns, Service class MachineAdmin(VersionAdmin): list_display = ('user','name','active') @@ -53,6 +53,9 @@ class InterfaceAdmin(VersionAdmin): class DomainAdmin(VersionAdmin): list_display = ('interface_parent', 'name', 'extension', 'cname') +class ServiceAdmin(VersionAdmin): + list_display = ('service_type', 'time_regen') + admin.site.register(Machine, MachineAdmin) admin.site.register(MachineType, MachineTypeAdmin) admin.site.register(IpType, IpTypeAdmin) @@ -62,3 +65,4 @@ admin.site.register(Ns, NsAdmin) admin.site.register(IpList, IpListAdmin) admin.site.register(Interface, InterfaceAdmin) admin.site.register(Domain, DomainAdmin) +admin.site.register(Service, ServiceAdmin) diff --git a/machines/forms.py b/machines/forms.py index bd71732d..668f8ac3 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -22,7 +22,7 @@ from django.forms import ModelForm, Form, ValidationError from django import forms -from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, IpType +from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Service, IpType from django.db.models import Q from django.core.validators import validate_email @@ -141,13 +141,9 @@ class MachineTypeForm(ModelForm): self.fields['type'].label = 'Type de machine à ajouter' self.fields['ip_type'].label = "Type d'ip relié" -class DelMachineTypeForm(ModelForm): +class DelMachineTypeForm(Form): machinetypes = forms.ModelMultipleChoiceField(queryset=MachineType.objects.all(), label="Types de machines actuelles", widget=forms.CheckboxSelectMultiple) - class Meta: - exclude = ['type','ip_type'] - model = MachineType - class IpTypeForm(ModelForm): class Meta: model = IpType @@ -161,7 +157,7 @@ class EditIpTypeForm(IpTypeForm): class Meta(IpTypeForm.Meta): fields = ['extension','type','need_infra'] -class DelIpTypeForm(forms.Form): +class DelIpTypeForm(Form): iptypes = forms.ModelMultipleChoiceField(queryset=IpType.objects.all(), label="Types d'ip actuelles", widget=forms.CheckboxSelectMultiple) class ExtensionForm(ModelForm): @@ -190,13 +186,9 @@ class MxForm(ModelForm): super(MxForm, self).__init__(*args, **kwargs) self.fields['name'].queryset = Domain.objects.exclude(interface_parent=None) -class DelMxForm(ModelForm): +class DelMxForm(Form): mx = forms.ModelMultipleChoiceField(queryset=Mx.objects.all(), label="MX actuels", widget=forms.CheckboxSelectMultiple) - class Meta: - exclude = ['zone', 'priority', 'name'] - model = Mx - class NsForm(ModelForm): class Meta: model = Ns @@ -206,9 +198,21 @@ class NsForm(ModelForm): super(NsForm, self).__init__(*args, **kwargs) self.fields['ns'].queryset = Domain.objects.exclude(interface_parent=None) -class DelNsForm(ModelForm): +class DelNsForm(Form): ns = forms.ModelMultipleChoiceField(queryset=Ns.objects.all(), label="Enregistrements NS actuels", widget=forms.CheckboxSelectMultiple) +class ServiceForm(ModelForm): class Meta: - exclude = ['zone', 'ns'] - model = Ns + model = Service + fields = '__all__' + + def save(self, commit=True): + instance = super(ServiceForm, self).save(commit=False) + if commit: + instance.save() + instance.process_link(self.cleaned_data.get('servers')) + return instance + +class DelServiceForm(Form): + service = forms.ModelMultipleChoiceField(queryset=Service.objects.all(), label="Services actuels", widget=forms.CheckboxSelectMultiple) + diff --git a/machines/migrations/0044_auto_20170808_0233.py b/machines/migrations/0044_auto_20170808_0233.py new file mode 100644 index 00000000..864505a2 --- /dev/null +++ b/machines/migrations/0044_auto_20170808_0233.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-08 00:33 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0043_auto_20170721_0350'), + ] + + operations = [ + migrations.CreateModel( + name='Service', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('service_type', models.CharField(blank=True, max_length=255, unique=True)), + ('time_regen', models.DurationField()), + ], + ), + migrations.CreateModel( + name='Service_link', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('last_regen', models.DateTimeField()), + ('asked_regen', models.BooleanField()), + ('server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.Interface')), + ('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='machines.Service')), + ], + ), + migrations.AddField( + model_name='service', + name='servers', + field=models.ManyToManyField(through='machines.Service_link', to='machines.Interface'), + ), + ] diff --git a/machines/migrations/0045_auto_20170808_0348.py b/machines/migrations/0045_auto_20170808_0348.py new file mode 100644 index 00000000..16a6bb0a --- /dev/null +++ b/machines/migrations/0045_auto_20170808_0348.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.7 on 2017-08-08 01:48 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('machines', '0044_auto_20170808_0233'), + ] + + operations = [ + migrations.AlterField( + model_name='service_link', + name='asked_regen', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='service_link', + name='last_regen', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/machines/models.py b/machines/models.py index d41bc6bb..44f0163f 100644 --- a/machines/models.py +++ b/machines/models.py @@ -25,6 +25,7 @@ from django.db.models.signals import post_save, pre_delete, post_delete from django.dispatch import receiver from django.forms import ValidationError from django.utils.functional import cached_property +from django.utils import timezone from macaddress.fields import MACAddressField from netaddr import mac_bare, EUI, IPSet, IPNetwork from django.core.validators import MinValueValidator,MaxValueValidator @@ -269,6 +270,48 @@ class IpList(models.Model): def __str__(self): return self.ipv4 +class Service(models.Model): + """ Definition d'un service (dhcp, dns, etc)""" + service_type = models.CharField(max_length=255, blank=True, unique=True) + time_regen = models.DurationField() + servers = models.ManyToManyField('Interface', through='Service_link') + + def ask_regen(self): + for serv in Service_link.objects.filter(service=self): + serv.asked_regen = True + serv.save() + + def process_link(self, servers): + for serv in servers.exclude(pk__in=Interface.objects.filter(service=self)): + link = Service_link(service=self, server=serv) + link.save() + for serv in Service_link.objects.filter(service=self).exclude(server__in=servers): + serv.delete() + return + + def save(self, *args, **kwargs): + super(Service, self).save(*args, **kwargs) + + def __str__(self): + return str(self.service_type) + +class Service_link(models.Model): + """ Definition du lien entre serveurs et services""" + service = models.ForeignKey('Service', on_delete=models.CASCADE) + server = models.ForeignKey('Interface', on_delete=models.CASCADE) + last_regen = models.DateTimeField(auto_now_add=True) + asked_regen = models.BooleanField(default=False) + + + def need_regen(self): + if self.asked_regen and (self.last_regen + self.service.time_regen) > timezone.now(): + return True + else: + return False + + def __str__(self): + return str(self.server) + " " + str(self.service) + @receiver(post_save, sender=Machine) def machine_post_save(sender, **kwargs): user = kwargs['instance'].user diff --git a/machines/templates/machines/aff_servers.html b/machines/templates/machines/aff_servers.html new file mode 100644 index 00000000..f29b620f --- /dev/null +++ b/machines/templates/machines/aff_servers.html @@ -0,0 +1,47 @@ +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + + + + + + + + + + + + {% for server in servers_list %} + + + + + + + + + {% endfor %} +
Nom du serviceServeurDernière régénérationRégénération nécessaireRégénération activée
{{ server.service }}{{ server.server }}{{ server.last_regen }}{{ server.asked_regen }}{{ server.need_regen }} +
+ diff --git a/machines/templates/machines/aff_service.html b/machines/templates/machines/aff_service.html new file mode 100644 index 00000000..f7f2b3f8 --- /dev/null +++ b/machines/templates/machines/aff_service.html @@ -0,0 +1,49 @@ +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + + + + + + + + + + + + {% for service in service_list %} + + + + + + + {% endfor %} +
Nom du serviceTemps minimum de régénérationServeurs inclus
{{ service.service_type }}{{ service.time_regen }}{% for serv in service.servers.all %}{{ serv }}, {% endfor %} + {% if is_infra %} + {% include 'buttons/edit.html' with href='machines:edit-service' id=service.id %} + {% endif %} + {% include 'buttons/history.html' with href='machines:history' name='service' id=service.id %} +
+ diff --git a/machines/templates/machines/index_service.html b/machines/templates/machines/index_service.html new file mode 100644 index 00000000..b07f994c --- /dev/null +++ b/machines/templates/machines/index_service.html @@ -0,0 +1,43 @@ +{% extends "machines/sidebar.html" %} +{% comment %} +Re2o est un logiciel d'administration développé initiallement au rezometz. Il +se veut agnostique au réseau considéré, de manière à être installable en +quelques clics. + +Copyright © 2017 Gabriel Détraz +Copyright © 2017 Goulven Kermarec +Copyright © 2017 Augustin Lemesle + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +{% endcomment %} + +{% load bootstrap3 %} + +{% block title %}Machines{% endblock %} + +{% block content %} +

Liste des services

+ {% if is_infra %} + Ajouter un service + Supprimer un ou plusieurs service + {% endif %} + {% include "machines/aff_service.html" with service_list=service_list %} +

Etat des serveurs

+ {% include "machines/aff_servers.html" with servers_list=servers_list %} +
+
+
+{% endblock %} + diff --git a/machines/templates/machines/sidebar.html b/machines/templates/machines/sidebar.html index 8261de3a..c4d98448 100644 --- a/machines/templates/machines/sidebar.html +++ b/machines/templates/machines/sidebar.html @@ -42,5 +42,9 @@ with this program; if not, write to the Free Software Foundation, Inc., Plages d'IP + + + Services (dhcp, dns...) + {% endif %} {% endblock %} diff --git a/machines/urls.py b/machines/urls.py index 65128581..1b352139 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -52,6 +52,10 @@ urlpatterns = [ url(r'^edit_alias/(?P[0-9]+)$', views.edit_alias, name='edit-alias'), url(r'^del_alias/(?P[0-9]+)$', views.del_alias, name='del-alias'), url(r'^index_alias/(?P[0-9]+)$', views.index_alias, name='index-alias'), + url(r'^add_service/$', views.add_service, name='add-service'), + url(r'^edit_service/(?P[0-9]+)$', views.edit_service, name='edit-service'), + url(r'^del_service/$', views.del_service, name='del-service'), + url(r'^index_service/$', views.index_service, name='index-service'), url(r'^history/(?Pmachine)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Pinterface)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Pmachinetype)/(?P[0-9]+)$', views.history, name='history'), @@ -60,6 +64,7 @@ urlpatterns = [ url(r'^history/(?Pns)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Piptype)/(?P[0-9]+)$', views.history, name='history'), url(r'^history/(?Palias)/(?P[0-9]+)$', views.history, name='history'), + url(r'^history/(?Pservice)/(?P[0-9]+)$', views.history, name='history'), url(r'^$', views.index, name='index'), url(r'^rest/mac-ip/$', views.mac_ip, name='mac-ip'), url(r'^rest/login/$', views.login_user, name='login'), diff --git a/machines/views.py b/machines/views.py index 9883f011..c8d66f43 100644 --- a/machines/views.py +++ b/machines/views.py @@ -44,8 +44,8 @@ from reversion.models import Version import re from .forms import NewMachineForm, EditMachineForm, EditInterfaceForm, AddInterfaceForm, MachineTypeForm, DelMachineTypeForm, ExtensionForm, DelExtensionForm, BaseEditInterfaceForm, BaseEditMachineForm -from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, DomainForm, AliasForm, DelAliasForm, NsForm, DelNsForm, MxForm, DelMxForm -from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain +from .forms import EditIpTypeForm, IpTypeForm, DelIpTypeForm, DomainForm, AliasForm, DelAliasForm, NsForm, DelNsForm, MxForm, DelMxForm, ServiceForm, DelServiceForm +from .models import IpType, Machine, Interface, IpList, MachineType, Extension, Mx, Ns, Domain, Service, Service_link from users.models import User from users.models import all_has_access from preferences.models import GeneralOption, OptionalMachine @@ -543,6 +543,55 @@ def del_alias(request, interfaceid): return redirect("/machines/index_alias/" + str(interfaceid)) return form({'machineform': alias, 'interfaceform': None}, 'machines/machine.html', request) + +@login_required +@permission_required('infra') +def add_service(request): + service = ServiceForm(request.POST or None) + if service.is_valid(): + with transaction.atomic(), reversion.create_revision(): + service.save() + reversion.set_user(request.user) + reversion.set_comment("Création") + messages.success(request, "Cet enregistrement service a été ajouté") + return redirect("/machines/index_service") + return form({'machineform': service}, 'machines/machine.html', request) + +@login_required +@permission_required('infra') +def edit_service(request, serviceid): + try: + service_instance = Service.objects.get(pk=serviceid) + except Ns.DoesNotExist: + messages.error(request, u"Entrée inexistante" ) + return redirect("/machines/index_extension/") + service = ServiceForm(request.POST or None, instance=service_instance) + if service.is_valid(): + with transaction.atomic(), reversion.create_revision(): + service.save() + reversion.set_user(request.user) + reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in service.changed_data)) + messages.success(request, "Service modifié") + return redirect("/machines/index_service/") + return form({'machineform': service}, 'machines/machine.html', request) + +@login_required +@permission_required('infra') +def del_service(request): + service = DelServiceForm(request.POST or None) + if service.is_valid(): + service_dels = service.cleaned_data['service'] + for service_del in service_dels: + try: + with transaction.atomic(), reversion.create_revision(): + service_del.delete() + reversion.set_user(request.user) + messages.success(request, "Le service a été supprimée") + except ProtectedError: + messages.error(request, "Erreur le service suivant %s ne peut être supprimé" % service_del) + return redirect("/machines/index_service") + return form({'machineform': service}, 'machines/machine.html', request) + @login_required @permission_required('cableur') def index(request): @@ -594,6 +643,13 @@ def index_alias(request, interfaceid): alias_list = Domain.objects.filter(cname=Domain.objects.filter(interface_parent=interface)).order_by('name') return render(request, 'machines/index_alias.html', {'alias_list':alias_list, 'interface_id': interfaceid}) +@login_required +@permission_required('cableur') +def index_service(request): + service_list = Service.objects.all() + servers_list = Service_link.objects.all() + return render(request, 'machines/index_service.html', {'service_list':service_list, 'servers_list':servers_list}) + @login_required def history(request, object, id): if object == 'machine': @@ -653,6 +709,12 @@ def history(request, object, id): except Ns.DoesNotExist: messages.error(request, "Ns inexistant") return redirect("/machines/") + elif object == 'service' and request.user.has_perms(('cableur',)): + try: + object_instance = Service.objects.get(pk=id) + except Service.DoesNotExist: + messages.error(request, "Service inexistant") + return redirect("/machines/") else: messages.error(request, "Objet inconnu") return redirect("/machines/")