diff --git a/api/serializers.py b/api/serializers.py
index 65a82eb6..94144b40 100644
--- a/api/serializers.py
+++ b/api/serializers.py
@@ -153,7 +153,8 @@ class VlanSerializer(NamespacedHMSerializer):
"""
class Meta:
model = machines.Vlan
- fields = ('vlan_id', 'name', 'comment', 'api_url')
+ fields = ('vlan_id', 'name', 'comment', 'arp_protect', 'dhcp_snooping',
+ 'dhcpv6_snooping', 'igmp', 'mld', 'api_url')
class NasSerializer(NamespacedHMSerializer):
@@ -310,6 +311,16 @@ class OuverturePortSerializer(NamespacedHMSerializer):
fields = ('begin', 'end', 'port_list', 'protocole', 'io', 'api_url')
+class RoleSerializer(NamespacedHMSerializer):
+ """Serialize `machines.models.OuverturePort` objects.
+ """
+ servers = InterfaceSerializer(read_only=True, many=True)
+
+ class Meta:
+ model = machines.Role
+ fields = ('role_type', 'servers', 'api_url')
+
+
# PREFERENCES
@@ -338,11 +349,15 @@ class OptionalMachineSerializer(NamespacedHMSerializer):
class OptionalTopologieSerializer(NamespacedHMSerializer):
"""Serialize `preferences.models.OptionalTopologie` objects.
"""
+ switchs_management_interface_ip = serializers.CharField()
class Meta:
model = preferences.OptionalTopologie
fields = ('radius_general_policy', 'vlan_decision_ok',
- 'vlan_decision_nok')
+ 'vlan_decision_nok', 'switchs_ip_type', 'switchs_web_management',
+ 'switchs_web_management_ssl', 'switchs_rest_management',
+ 'switchs_management_utils', 'switchs_management_interface_ip',
+ 'provision_switchs_enabled', 'switchs_provision', 'switchs_management_sftp_creds')
class GeneralOptionSerializer(NamespacedHMSerializer):
@@ -467,16 +482,30 @@ class BuildingSerializer(NamespacedHMSerializer):
class SwitchPortSerializer(NamespacedHMSerializer):
"""Serialize `topologie.models.Port` objects
"""
+
+ get_port_profil = NamespacedHIField(view_name='portprofile-detail', read_only=True)
+
class Meta:
model = topologie.Port
fields = ('switch', 'port', 'room', 'machine_interface', 'related',
- 'custom_profile', 'state', 'details', 'api_url')
+ 'custom_profile', 'state', 'get_port_profil', 'details', 'api_url')
extra_kwargs = {
'related': {'view_name': 'switchport-detail'},
'api_url': {'view_name': 'switchport-detail'},
}
+class PortProfileSerializer(NamespacedHMSerializer):
+ """Serialize `topologie.models.Room` objects
+ """
+ class Meta:
+ model = topologie.PortProfile
+ fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged',
+ 'radius_type', 'radius_mode', 'speed', 'mac_limit', 'flow_control',
+ 'dhcp_snooping', 'dhcpv6_snooping', 'dhcpv6_snooping', 'arp_protect',
+ 'ra_guard', 'loop_protect', 'api_url')
+
+
class RoomSerializer(NamespacedHMSerializer):
"""Serialize `topologie.models.Room` objects
"""
@@ -644,6 +673,89 @@ class ServiceRegenSerializer(NamespacedHMSerializer):
'api_url': {'view_name': 'serviceregen-detail'}
}
+# Switches et ports
+
+class InterfaceVlanSerializer(NamespacedHMSerializer):
+ domain = serializers.CharField(read_only=True)
+ ipv4 = serializers.CharField(read_only=True)
+ ipv6 = Ipv6ListSerializer(read_only=True, many=True)
+ vlan_id = serializers.IntegerField(source='type.ip_type.vlan.vlan_id', read_only=True)
+
+ class Meta:
+ model = machines.Interface
+ fields = ('ipv4', 'ipv6', 'domain', 'vlan_id')
+
+class InterfaceRoleSerializer(NamespacedHMSerializer):
+ interface = InterfaceVlanSerializer(source='machine.interface_set', read_only=True, many=True)
+
+ class Meta:
+ model = machines.Interface
+ fields = ('interface',)
+
+
+class RoleSerializer(NamespacedHMSerializer):
+ """Serialize `machines.models.OuverturePort` objects.
+ """
+ servers = InterfaceRoleSerializer(read_only=True, many=True)
+
+ class Meta:
+ model = machines.Role
+ fields = ('role_type', 'servers', 'specific_role')
+
+
+class VlanPortSerializer(NamespacedHMSerializer):
+ class Meta:
+ model = machines.Vlan
+ fields = ('vlan_id', 'name')
+
+
+class ProfilSerializer(NamespacedHMSerializer):
+ vlan_untagged = VlanSerializer(read_only=True)
+ vlan_tagged = VlanPortSerializer(read_only=True, many=True)
+
+ class Meta:
+ model = topologie.PortProfile
+ fields = ('name', 'profil_default', 'vlan_untagged', 'vlan_tagged', 'radius_type', 'radius_mode', 'speed', 'mac_limit', 'flow_control', 'dhcp_snooping', 'dhcpv6_snooping', 'arp_protect', 'ra_guard', 'loop_protect', 'vlan_untagged', 'vlan_tagged')
+
+
+class ModelSwitchSerializer(NamespacedHMSerializer):
+ constructor = serializers.CharField(read_only=True)
+
+ class Meta:
+ model = topologie.ModelSwitch
+ fields = ('reference', 'firmware', 'constructor')
+
+
+class SwitchBaySerializer(NamespacedHMSerializer):
+ class Meta:
+ model = topologie.SwitchBay
+ fields = ('name',)
+
+
+class PortsSerializer(NamespacedHMSerializer):
+ """Serialize `machines.models.Ipv6List` objects.
+ """
+ get_port_profil = ProfilSerializer(read_only=True)
+
+
+ class Meta:
+ model = topologie.Port
+ fields = ('state', 'port', 'pretty_name', 'get_port_profil')
+
+
+
+class SwitchPortSerializer(serializers.ModelSerializer):
+ """Serialize the data about the switches"""
+ ports = PortsSerializer(many=True, read_only=True)
+ model = ModelSwitchSerializer(read_only=True)
+ switchbay = SwitchBaySerializer(read_only=True)
+
+
+ class Meta:
+ model = topologie.Switch
+ fields = ('short_name', 'model', 'switchbay', 'ports', 'ipv4', 'ipv6',
+ 'interfaces_subnet', 'interfaces6_subnet', 'automatic_provision', 'rest_enabled',
+ 'web_management_enabled', 'get_radius_key_value', 'get_management_cred_value')
# LOCAL EMAILS
diff --git a/api/urls.py b/api/urls.py
index abc466e1..e8c63657 100644
--- a/api/urls.py
+++ b/api/urls.py
@@ -63,6 +63,7 @@ router.register_viewset(r'machines/service', views.ServiceViewSet)
router.register_viewset(r'machines/servicelink', views.ServiceLinkViewSet, base_name='servicelink')
router.register_viewset(r'machines/ouvertureportlist', views.OuverturePortListViewSet)
router.register_viewset(r'machines/ouvertureport', views.OuverturePortViewSet)
+router.register_viewset(r'machines/role', views.RoleViewSet)
# PREFERENCES
router.register_view(r'preferences/optionaluser', views.OptionalUserView),
router.register_view(r'preferences/optionalmachine', views.OptionalMachineView),
@@ -81,7 +82,8 @@ router.register_viewset(r'topologie/modelswitch', views.ModelSwitchViewSet)
router.register_viewset(r'topologie/constructorswitch', views.ConstructorSwitchViewSet)
router.register_viewset(r'topologie/switchbay', views.SwitchBayViewSet)
router.register_viewset(r'topologie/building', views.BuildingViewSet)
-router.register(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
+router.register_viewset(r'topologie/switchport', views.SwitchPortViewSet, base_name='switchport')
+router.register_viewset(r'topologie/portprofile', views.PortProfileViewSet, base_name='portprofile')
router.register_viewset(r'topologie/room', views.RoomViewSet)
router.register(r'topologie/portprofile', views.PortProfileViewSet)
# USERS
@@ -105,6 +107,9 @@ router.register_view(r'localemail/users', views.LocalEmailUsersView),
# Firewall
router.register_view(r'firewall/subnet-ports', views.SubnetPortsOpenView),
router.register_view(r'firewall/interface-ports', views.InterfacePortsOpenView),
+# Switches config
+router.register_view(r'switchs/ports-config', views.SwitchPortView),
+router.register_view(r'switchs/role', views.RoleView),
# DNS
router.register_view(r'dns/zones', views.DNSZonesView),
router.register_view(r'dns/reverse-zones', views.DNSReverseZonesView),
diff --git a/api/views.py b/api/views.py
index 6e5265f6..22901d09 100644
--- a/api/views.py
+++ b/api/views.py
@@ -242,6 +242,13 @@ class OuverturePortViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = serializers.OuverturePortSerializer
+class RoleViewSet(viewsets.ReadOnlyModelViewSet):
+ """Exposes list and details of `machines.models.Machine` objects.
+ """
+ queryset = machines.Role.objects.all()
+ serializer_class = serializers.RoleSerializer
+
+
# PREFERENCES
# Those views differ a bit because there is only one object
# to display, so we don't bother with the listing part
@@ -397,6 +404,13 @@ class SwitchPortViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = serializers.SwitchPortSerializer
+class PortProfileViewSet(viewsets.ReadOnlyModelViewSet):
+ """Exposes list and details of `topologie.models.PortProfile` objects.
+ """
+ queryset = topologie.PortProfile.objects.all()
+ serializer_class = serializers.PortProfileSerializer
+
+
class RoomViewSet(viewsets.ReadOnlyModelViewSet):
"""Exposes list and details of `topologie.models.Room` objects.
"""
@@ -515,6 +529,24 @@ class ServiceRegenViewSet(viewsets.ModelViewSet):
queryset = queryset.filter(server__domain__name__iexact=hostname)
return queryset
+# Config des switches
+
+class SwitchPortView(generics.ListAPIView):
+ """Exposes the associations between hostname, mac address and IPv4 in
+ order to build the DHCP lease files.
+ """
+ queryset = topologie.Switch.objects.all().select_related("switchbay").select_related("model__constructor").prefetch_related("ports__custom_profile__vlan_tagged").prefetch_related("ports__custom_profile__vlan_untagged").prefetch_related("ports__machine_interface__domain__extension").prefetch_related("ports__room")
+
+ serializer_class = serializers.SwitchPortSerializer
+
+
+class RoleView(generics.ListAPIView):
+ """Exposes the associations between hostname, mac address and IPv4 in
+ order to build the DHCP lease files.
+ """
+ queryset = machines.Role.objects.all().prefetch_related('servers')
+ serializer_class = serializers.RoleSerializer
+
# LOCAL EMAILS
diff --git a/machines/forms.py b/machines/forms.py
index 4af060d3..e9c75d59 100644
--- a/machines/forms.py
+++ b/machines/forms.py
@@ -576,13 +576,24 @@ class VlanForm(FormRevMixin, ModelForm):
"""Ajout d'un vlan : id, nom"""
class Meta:
model = Vlan
- fields = '__all__'
+ fields = ['vlan_id', 'name', 'comment']
def __init__(self, *args, **kwargs):
prefix = kwargs.pop('prefix', self.Meta.model.__name__)
super(VlanForm, self).__init__(*args, prefix=prefix, **kwargs)
+class EditOptionVlanForm(FormRevMixin, ModelForm):
+ """Ajout d'un vlan : id, nom"""
+ class Meta:
+ model = Vlan
+ fields = ['dhcp_snooping', 'dhcpv6_snooping', 'arp_protect', 'igmp', 'mld']
+
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(EditOptionVlanForm, self).__init__(*args, prefix=prefix, **kwargs)
+
+
class DelVlanForm(FormRevMixin, Form):
"""Suppression d'un ou plusieurs vlans"""
vlan = forms.ModelMultipleChoiceField(
diff --git a/machines/migrations/0095_auto_20180919_2225.py b/machines/migrations/0095_auto_20180919_2225.py
new file mode 100644
index 00000000..66c082ff
--- /dev/null
+++ b/machines/migrations/0095_auto_20180919_2225.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-09-19 20:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('machines', '0094_auto_20180815_1918'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='vlan',
+ name='arp_protect',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='vlan',
+ name='dhcp_snooping',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='vlan',
+ name='dhcpv6_snooping',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='vlan',
+ name='igmp',
+ field=models.BooleanField(default=False, help_text='Gestion multicast v4'),
+ ),
+ migrations.AddField(
+ model_name='vlan',
+ name='mld',
+ field=models.BooleanField(default=False, help_text='Gestion multicast v6'),
+ ),
+ ]
diff --git a/machines/models.py b/machines/models.py
index 4de0b012..040043a1 100644
--- a/machines/models.py
+++ b/machines/models.py
@@ -201,6 +201,12 @@ class Machine(RevMixin, FieldPermissionModelMixin, models.Model):
de cette machine"""
return str(self.interface_set.first().domain.name)
+ @cached_property
+ def complete_name(self):
+ """Par defaut, renvoie le nom de la première interface
+ de cette machine"""
+ return str(self.interface_set.first())
+
@cached_property
def all_short_names(self):
"""Renvoie de manière unique, le nom des interfaces de cette
@@ -515,6 +521,18 @@ class Vlan(RevMixin, AclMixin, models.Model):
vlan_id = models.PositiveIntegerField(validators=[MaxValueValidator(4095)])
name = models.CharField(max_length=256)
comment = models.CharField(max_length=256, blank=True)
+ #Réglages supplémentaires
+ arp_protect = models.BooleanField(default=False)
+ dhcp_snooping = models.BooleanField(default=False)
+ dhcpv6_snooping = models.BooleanField(default=False)
+ igmp = models.BooleanField(
+ default=False,
+ help_text="Gestion multicast v4"
+ )
+ mld = models.BooleanField(
+ default=False,
+ help_text="Gestion multicast v6"
+ )
class Meta:
permissions = (
@@ -1634,6 +1652,19 @@ class Role(RevMixin, AclMixin, models.Model):
machine__interface__role=cls.objects.filter(specific_role=roletype)
)
+ @classmethod
+ def get_instance(cls, machineid, *_args, **_kwargs):
+ """Get the Machine instance with machineid.
+ :param userid: The id
+ :return: The user
+ """
+ return cls.objects.get(pk=machineid)
+
+ @classmethod
+ def interface_for_roletype(cls, roletype):
+ """Return interfaces for a roletype"""
+ return Interface.objects.filter(role=cls.objects.filter(specific_role=roletype))
+
def save(self, *args, **kwargs):
super(Role, self).save(*args, **kwargs)
diff --git a/preferences/admin.py b/preferences/admin.py
index 5ca90095..b7b171d0 100644
--- a/preferences/admin.py
+++ b/preferences/admin.py
@@ -37,7 +37,10 @@ from .models import (
MailContact,
AssoOption,
MailMessageOption,
- HomeOption
+ HomeOption,
+ RadiusKey,
+ SwitchManagementCred,
+ Reminder
)
@@ -86,6 +89,18 @@ class HomeOptionAdmin(VersionAdmin):
pass
+class RadiusKeyAdmin(VersionAdmin):
+ """Class radiuskey"""
+ pass
+
+class SwitchManagementCredAdmin(VersionAdmin):
+ """Class managementcred for switch"""
+ pass
+
+class ReminderAdmin(VersionAdmin):
+ """Class reminder for switch"""
+ pass
+
admin.site.register(OptionalUser, OptionalUserAdmin)
admin.site.register(OptionalMachine, OptionalMachineAdmin)
admin.site.register(OptionalTopologie, OptionalTopologieAdmin)
@@ -93,5 +108,8 @@ admin.site.register(GeneralOption, GeneralOptionAdmin)
admin.site.register(HomeOption, HomeOptionAdmin)
admin.site.register(Service, ServiceAdmin)
admin.site.register(MailContact, MailContactAdmin)
+admin.site.register(Reminder, ReminderAdmin)
+admin.site.register(RadiusKey, RadiusKeyAdmin)
+admin.site.register(SwitchManagementCred, SwitchManagementCredAdmin)
admin.site.register(AssoOption, AssoOptionAdmin)
admin.site.register(MailMessageOption, MailMessageOptionAdmin)
diff --git a/preferences/forms.py b/preferences/forms.py
index 73731750..5933107c 100644
--- a/preferences/forms.py
+++ b/preferences/forms.py
@@ -38,9 +38,12 @@ from .models import (
MailMessageOption,
HomeOption,
Service,
- MailContact
+ MailContact,
+ Reminder,
+ RadiusKey,
+ SwitchManagementCred,
)
-
+from topologie.models import Switch
class EditOptionalUserForm(ModelForm):
"""Formulaire d'édition des options de l'user. (solde, telephone..)"""
@@ -92,7 +95,14 @@ class EditOptionalMachineForm(ModelForm):
class EditOptionalTopologieForm(ModelForm):
- """Options de topologie, formulaire d'edition (vlan par default etc)"""
+ """Options de topologie, formulaire d'edition (vlan par default etc)
+ On rajoute un champ automatic provision switchs pour gérer facilement
+ l'ajout de switchs au provisionning automatique"""
+ automatic_provision_switchs = forms.ModelMultipleChoiceField(
+ Switch.objects.all(),
+ required=False
+ )
+
class Meta:
model = OptionalTopologie
fields = '__all__'
@@ -110,6 +120,14 @@ class EditOptionalTopologieForm(ModelForm):
self.fields['vlan_decision_nok'].label = _("VLAN for machines rejected"
" by RADIUS")
+ self.initial['automatic_provision_switchs'] = Switch.objects.filter(automatic_provision=True)
+
+ def save(self, commit=True):
+ instance = super().save(commit)
+ Switch.objects.all().update(automatic_provision=False)
+ self.cleaned_data['automatic_provision_switchs'].update(automatic_provision=True)
+ return instance
+
class EditGeneralOptionForm(ModelForm):
"""Options générales (affichages de résultats de recherche, etc)"""
@@ -242,8 +260,68 @@ class DelServiceForm(Form):
else:
self.fields['services'].queryset = Service.objects.all()
-class MailContactForm(FormRevMixin, ModelForm):
- """Edit and add contact email adress"""
+class ReminderForm(FormRevMixin, ModelForm):
+ """Edition, ajout de services sur la page d'accueil"""
+ class Meta:
+ model = Reminder
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(ReminderForm, self).__init__(*args, prefix=prefix, **kwargs)
+
+
+class RadiusKeyForm(FormRevMixin, ModelForm):
+ """Edition, ajout de clef radius"""
+ members = forms.ModelMultipleChoiceField(
+ queryset=Switch.objects.all(),
+ required=False
+ )
+
+ class Meta:
+ model = RadiusKey
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(RadiusKeyForm, self).__init__(*args, prefix=prefix, **kwargs)
+ instance = kwargs.get('instance', None)
+ if instance:
+ self.initial['members'] = Switch.objects.filter(radius_key=instance)
+
+ def save(self, commit=True):
+ instance = super().save(commit)
+ instance.switch_set = self.cleaned_data['members']
+ return instance
+
+
+class SwitchManagementCredForm(FormRevMixin, ModelForm):
+ """Edition, ajout de creds de management pour gestion
+ et interface rest des switchs"""
+ members = forms.ModelMultipleChoiceField(
+ Switch.objects.all(),
+ required=False
+ )
+
+ class Meta:
+ model = SwitchManagementCred
+ fields = '__all__'
+
+ def __init__(self, *args, **kwargs):
+ prefix = kwargs.pop('prefix', self.Meta.model.__name__)
+ super(SwitchManagementCredForm, self).__init__(*args, prefix=prefix, **kwargs)
+ instance = kwargs.get('instance', None)
+ if instance:
+ self.initial['members'] = Switch.objects.filter(management_creds=instance)
+
+ def save(self, commit=True):
+ instance = super().save(commit)
+ instance.switch_set = self.cleaned_data['members']
+ return instance
+
+
+class MailContactForm(ModelForm):
+ """Edition, ajout d'adresse de contact"""
class Meta:
model = MailContact
fields = '__all__'
diff --git a/preferences/migrations/0051_auto_20180919_2225.py b/preferences/migrations/0051_auto_20180919_2225.py
new file mode 100644
index 00000000..f776a9a6
--- /dev/null
+++ b/preferences/migrations/0051_auto_20180919_2225.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-09-19 20:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import re2o.aes_field
+import re2o.mixins
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('machines', '0095_auto_20180919_2225'),
+ ('preferences', '0050_auto_20180818_1329'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='RadiusKey',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('radius_key', re2o.aes_field.AESEncryptedField(help_text='Clef radius', max_length=255)),
+ ('comment', models.CharField(blank=True, help_text='Commentaire de cette clef', max_length=255, null=True)),
+ ('default_switch', models.BooleanField(default=True, help_text='Clef par défaut des switchs', unique=True)),
+ ],
+ options={
+ 'permissions': (('view_radiuskey', 'Peut voir un objet radiuskey'),),
+ },
+ bases=(re2o.mixins.AclMixin, models.Model),
+ ),
+ migrations.CreateModel(
+ name='Reminder',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('days', models.IntegerField(default=7, help_text="Délais entre le mail et la fin d'adhésion", unique=True)),
+ ('message', models.CharField(blank=True, default='', help_text='Message affiché spécifiquement pour ce rappel', max_length=255, null=True)),
+ ],
+ options={
+ 'permissions': (('view_reminder', 'Peut voir un objet reminder'),),
+ },
+ bases=(re2o.mixins.AclMixin, models.Model),
+ ),
+ migrations.CreateModel(
+ name='SwitchManagementCred',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('management_id', models.CharField(help_text='Login du switch', max_length=63)),
+ ('management_pass', re2o.aes_field.AESEncryptedField(help_text='Mot de passe', max_length=63)),
+ ('default_switch', models.BooleanField(default=True, help_text='Creds par défaut des switchs', unique=True)),
+ ],
+ options={
+ 'permissions': (('view_switchmanagementcred', 'Peut voir un objet switchmanagementcred'),),
+ },
+ bases=(re2o.mixins.AclMixin, models.Model),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='sftp_login',
+ field=models.CharField(blank=True, help_text='Login sftp des switchs', max_length=32, null=True),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='sftp_pass',
+ field=re2o.aes_field.AESEncryptedField(blank=True, help_text='Mot de passe sftp', max_length=63, null=True),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='switchs_ip_type',
+ field=models.OneToOneField(blank=True, help_text="Plage d'ip de management des switchs", null=True, on_delete=django.db.models.deletion.PROTECT, to='machines.IpType'),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='switchs_provision',
+ field=models.CharField(choices=[('sftp', 'sftp'), ('tftp', 'tftp')], default='tftp', help_text='Mode de récupération des confs par les switchs', max_length=32),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='switchs_rest_management',
+ field=models.BooleanField(default=False, help_text='Rest management, activé si provision auto'),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='switchs_web_management',
+ field=models.BooleanField(default=False, help_text='Web management, activé si provision automatique'),
+ ),
+ migrations.AddField(
+ model_name='optionaltopologie',
+ name='switchs_web_management_ssl',
+ field=models.BooleanField(default=False, help_text='Web management ssl. Assurez-vous que un certif est installé sur le switch !'),
+ ),
+ migrations.AlterField(
+ model_name='mailmessageoption',
+ name='welcome_mail_en',
+ field=models.TextField(default='', help_text='Mail de bienvenue en anglais'),
+ ),
+ migrations.AlterField(
+ model_name='mailmessageoption',
+ name='welcome_mail_fr',
+ field=models.TextField(default='', help_text='Mail de bienvenue en français'),
+ ),
+ ]
diff --git a/preferences/models.py b/preferences/models.py
index 3199dd6c..31d382aa 100644
--- a/preferences/models.py
+++ b/preferences/models.py
@@ -34,7 +34,10 @@ from django.forms import ValidationError
from django.utils.translation import ugettext_lazy as _
import machines.models
+
from re2o.mixins import AclMixin
+from re2o.aes_field import AESEncryptedField
+from datetime import timedelta
class PreferencesModel(models.Model):
@@ -180,6 +183,10 @@ class OptionalTopologie(AclMixin, PreferencesModel):
(MACHINE, _("On the IP range's VLAN of the machine")),
(DEFINED, _("Preset in 'VLAN for machines accepted by RADIUS'")),
)
+ CHOICE_PROVISION = (
+ ('sftp', 'sftp'),
+ ('tftp', 'tftp'),
+ )
radius_general_policy = models.CharField(
max_length=32,
@@ -200,6 +207,97 @@ class OptionalTopologie(AclMixin, PreferencesModel):
blank=True,
null=True
)
+ switchs_web_management = models.BooleanField(
+ default=False,
+ help_text="Web management, activé si provision automatique"
+ )
+ switchs_web_management_ssl = models.BooleanField(
+ default=False,
+ help_text="Web management ssl. Assurez-vous que un certif est installé sur le switch !"
+ )
+ switchs_rest_management = models.BooleanField(
+ default=False,
+ help_text="Rest management, activé si provision auto"
+ )
+ switchs_ip_type = models.OneToOneField(
+ 'machines.IpType',
+ on_delete=models.PROTECT,
+ blank=True,
+ null=True,
+ help_text="Plage d'ip de management des switchs"
+ )
+ switchs_provision = models.CharField(
+ max_length=32,
+ choices=CHOICE_PROVISION,
+ default='tftp',
+ help_text="Mode de récupération des confs par les switchs"
+ )
+ sftp_login = models.CharField(
+ max_length=32,
+ null=True,
+ blank=True,
+ help_text="Login sftp des switchs"
+ )
+ sftp_pass = AESEncryptedField(
+ max_length=63,
+ null=True,
+ blank=True,
+ help_text="Mot de passe sftp"
+ )
+
+ @cached_property
+ def provisioned_switchs(self):
+ """Liste des switches provisionnés"""
+ from topologie.models import Switch
+ return Switch.objects.filter(automatic_provision=True)
+
+ @cached_property
+ def switchs_management_interface(self):
+ """Return the ip of the interface that the switch have to contact to get it's config"""
+ if self.switchs_ip_type:
+ from machines.models import Role, Interface
+ return Interface.objects.filter(machine__interface__in=Role.interface_for_roletype("switch-conf-server")).filter(type__ip_type=self.switchs_ip_type).first()
+ else:
+ return None
+
+ @cached_property
+ def switchs_management_interface_ip(self):
+ """Same, but return the ipv4"""
+ if not self.switchs_management_interface:
+ return None
+ return self.switchs_management_interface.ipv4
+
+ @cached_property
+ def switchs_management_sftp_creds(self):
+ """Credentials des switchs pour provion sftp"""
+ if self.sftp_login and self.sftp_pass:
+ return {'login' : self.sftp_login, 'pass' : self.sftp_pass}
+ else:
+ return None
+
+ @cached_property
+ def switchs_management_utils(self):
+ """Used for switch_conf, return a list of ip on vlans"""
+ from machines.models import Role, Ipv6List, Interface
+ def return_ips_dict(interfaces):
+ return {'ipv4' : [str(interface.ipv4) for interface in interfaces], 'ipv6' : Ipv6List.objects.filter(interface__in=interfaces).values_list('ipv6', flat=True)}
+
+ ntp_servers = Role.all_interfaces_for_roletype("ntp-server").filter(type__ip_type=self.switchs_ip_type)
+ log_servers = Role.all_interfaces_for_roletype("log-server").filter(type__ip_type=self.switchs_ip_type)
+ radius_servers = Role.all_interfaces_for_roletype("radius-server").filter(type__ip_type=self.switchs_ip_type)
+ dhcp_servers = Role.all_interfaces_for_roletype("dhcp-server")
+ subnet = None
+ subnet6 = None
+ if self.switchs_ip_type:
+ subnet = self.switchs_ip_type.ip_set_full_info
+ subnet6 = self.switchs_ip_type.ip6_set_full_info
+ return {'ntp_servers': return_ips_dict(ntp_servers), 'log_servers': return_ips_dict(log_servers), 'radius_servers': return_ips_dict(radius_servers), 'dhcp_servers': return_ips_dict(dhcp_servers), 'subnet': subnet, 'subnet6': subnet6}
+
+ @cached_property
+ def provision_switchs_enabled(self):
+ """Return true if all settings are ok : switchs on automatic provision,
+ ip_type"""
+ return bool(self.provisioned_switchs and self.switchs_ip_type and SwitchManagementCred.objects.filter(default_switch=True).exists() and self.switchs_management_interface_ip and bool(self.switchs_provision != 'sftp' or self.switchs_management_sftp_creds))
class Meta:
permissions = (
@@ -215,6 +313,91 @@ def optionaltopologie_post_save(**kwargs):
topologie_pref.set_in_cache()
+class RadiusKey(AclMixin, models.Model):
+ """Class of a radius key"""
+ radius_key = AESEncryptedField(
+ max_length=255,
+ help_text="Clef radius"
+ )
+ comment = models.CharField(
+ max_length=255,
+ null=True,
+ blank=True,
+ help_text="Commentaire de cette clef"
+ )
+ default_switch = models.BooleanField(
+ default=True,
+ unique=True,
+ help_text= "Clef par défaut des switchs"
+ )
+
+ class Meta:
+ permissions = (
+ ("view_radiuskey", "Peut voir un objet radiuskey"),
+ )
+
+ def __str__(self):
+ return "Clef radius " + str(self.id) + " " + str(self.comment)
+
+
+class SwitchManagementCred(AclMixin, models.Model):
+ """Class of a management creds of a switch, for rest management"""
+ management_id = models.CharField(
+ max_length=63,
+ help_text="Login du switch"
+ )
+ management_pass = AESEncryptedField(
+ max_length=63,
+ help_text="Mot de passe"
+ )
+ default_switch = models.BooleanField(
+ default=True,
+ unique=True,
+ help_text= "Creds par défaut des switchs"
+ )
+
+ class Meta:
+ permissions = (
+ ("view_switchmanagementcred", "Peut voir un objet switchmanagementcred"),
+ )
+
+ def __str__(self):
+ return "Identifiant " + str(self.management_id)
+
+
+class Reminder(AclMixin, models.Model):
+ """Options pour les mails de notification de fin d'adhésion.
+ Days: liste des nombres de jours pour lesquells un mail est envoyé
+ optionalMessage: message additionel pour le mail
+ """
+ PRETTY_NAME="Options pour le mail de fin d'adhésion"
+
+ days = models.IntegerField(
+ default=7,
+ unique=True,
+ help_text="Délais entre le mail et la fin d'adhésion"
+ )
+ message = models.CharField(
+ max_length=255,
+ default="",
+ null=True,
+ blank=True,
+ help_text="Message affiché spécifiquement pour ce rappel"
+ )
+
+ class Meta:
+ permissions = (
+ ("view_reminder", "Peut voir un objet reminder"),
+ )
+
+ def users_to_remind(self):
+ from re2o.utils import all_has_access
+ date = timezone.now().replace(minute=0,hour=0)
+ futur_date = date + timedelta(days=self.days)
+ users = all_has_access(futur_date).exclude(pk__in = all_has_access(futur_date + timedelta(days=1)))
+ return users
+
+
class GeneralOption(AclMixin, PreferencesModel):
"""Options générales : nombre de resultats par page, nom du site,
temps où les liens sont valides"""
@@ -383,8 +566,8 @@ def homeoption_post_save(**kwargs):
class MailMessageOption(AclMixin, models.Model):
"""Reglages, mail de bienvenue et autre"""
- welcome_mail_fr = models.TextField(default="")
- welcome_mail_en = models.TextField(default="")
+ welcome_mail_fr = models.TextField(default="", help_text="Mail de bienvenue en français")
+ welcome_mail_en = models.TextField(default="", help_text="Mail de bienvenue en anglais")
class Meta:
permissions = (
diff --git a/preferences/templates/preferences/aff_radiuskey.html b/preferences/templates/preferences/aff_radiuskey.html
new file mode 100644
index 00000000..0d58efe3
--- /dev/null
+++ b/preferences/templates/preferences/aff_radiuskey.html
@@ -0,0 +1,57 @@
+{% 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 acl %}
+{% load logs_extra %}
+
+
+
+ Id Clef |
+ Commentaire |
+ Clef par default des switchs |
+ Clef utilisée par les switchs |
+ |
+ |
+
+
+ {% for radiuskey in radiuskey_list %}
+
+ {{ radiuskey.id }} |
+ {{ radiuskey.comment }} |
+ {{ radiuskey.default_switch }} |
+ {{ radiuskey.switch_set.all|join:", " }} |
+
+ {% can_edit radiuskey %}
+ {% include 'buttons/edit.html' with href='preferences:edit-radiuskey' id=radiuskey.id %}
+ {% acl_end %}
+ {% can_delete radiuskey %}
+
+
+
+ {% acl_end %}
+ {% history_button radiuskey %}
+ |
+
+ {% endfor %}
+
+
diff --git a/preferences/templates/preferences/aff_switchmanagementcred.html b/preferences/templates/preferences/aff_switchmanagementcred.html
new file mode 100644
index 00000000..ef8b0143
--- /dev/null
+++ b/preferences/templates/preferences/aff_switchmanagementcred.html
@@ -0,0 +1,55 @@
+{% 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 acl %}
+{% load logs_extra %}
+
+
+
+ Identifiant |
+ Creds par default des switchs |
+ Utilisé pour les switchs |
+ |
+ |
+
+
+ {% for switchmanagementcred in switchmanagementcred_list %}
+
+ {{ switchmanagementcred.management_id }} |
+ {{ switchmanagementcred.default_switch }} |
+ {{ switchmanagementcred.switch_set.all|join:", " }} |
+
+ {% can_edit switchmanagementcred %}
+ {% include 'buttons/edit.html' with href='preferences:edit-switchmanagementcred' id=switchmanagementcred.id %}
+ {% acl_end %}
+ {% can_delete switchmanagementcred %}
+
+
+
+ {% acl_end %}
+ {% history_button switchmanagementcred %}
+ |
+
+ {% endfor %}
+
+
diff --git a/preferences/templates/preferences/delete.html b/preferences/templates/preferences/delete.html
new file mode 100644
index 00000000..ce277647
--- /dev/null
+++ b/preferences/templates/preferences/delete.html
@@ -0,0 +1,40 @@
+{% extends "topologie/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 %}Création et modification de machines{% endblock %}
+
+{% block content %}
+
+
+
+
+
+{% endblock %}
diff --git a/preferences/templates/preferences/display_preferences.html b/preferences/templates/preferences/display_preferences.html
index 6a499969..0cba4d78 100644
--- a/preferences/templates/preferences/display_preferences.html
+++ b/preferences/templates/preferences/display_preferences.html
@@ -118,6 +118,75 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "VLAN for machines rejected by RADIUS" %} |
{{ topologieoptions.vlan_decision_nok }} |
+
+ Placement sur ce vlan par default en cas de rejet |
+ {{ topologieoptions.vlan_decision_nok }} |
+
+
+
+ Clef radius
+ {% can_create RadiusKey%}
+ Ajouter une clef radius
+ {% acl_end %}
+ {% include "preferences/aff_radiuskey.html" with radiuskey_list=radiuskey_list %}
+
+ Configuration des switches
+
+
+ Web management, activé si provision automatique |
+ {{ topologieoptions.switchs_web_management }} |
+ Rest management, activé si provision auto |
+ {{ topologieoptions.switchs_rest_management }} |
+
+
+
+
+
+ {% if topologieoptions.provision_switchs_enabled %}Provision de la config des switchs{% else %}Provision de la config des switchs{% endif%}
+
+
+ Switchs configurés automatiquement |
+ {{ topologieoptions.provisioned_switchs|join:", " }} {% if topologieoptions.provisioned_switchs %} OK{% else %}Manquant{% endif %} |
+
+
+ Plage d'ip de management des switchs |
+ {{ topologieoptions.switchs_ip_type }} {% if topologieoptions.switchs_ip_type %} OK{% else %}Manquant{% endif %} |
+
+
+ Serveur des config des switchs |
+ {{ topologieoptions.switchs_management_interface }} {% if topologieoptions.switchs_management_interface %} - {{ topologieoptions.switchs_management_interface_ip }} OK{% else %}Manquant{% endif %} |
+
+
+ Mode de provision des switchs |
+ {{ topologieoptions.switchs_provision }} |
+
+
+ Mode TFTP |
+ OK |
+
+
+ Mode SFTP |
+ {% if topologieoptions.switchs_management_sftp_creds %} OK{% else %}Creds manquants{% endif %} |
+
+
+
+ Creds de management des switchs
+ {% can_create SwitchManagementCred%}
+ Ajouter un id/mdp de management switch
+ {% acl_end %}
+
+
+ {% if switchmanagementcred_list %} OK{% else %}Manquant{% endif %}
+ {% include "preferences/aff_switchmanagementcred.html" with switchmanagementcred_list=switchmanagementcred_list %}
+
+
+
+ {% if topologieoptions.provisioned_switchs %}Provision de la config des switchs{% else %}Provision de la config des switchs{% endif%}
+
+
+ Switchs configurés automatiquement |
+ {{ topologieoptions.provisioned_switchs|join:", " }} |
+
{% trans "General preferences" %}
diff --git a/preferences/templates/preferences/edit_preferences.html b/preferences/templates/preferences/edit_preferences.html
index 30ab2423..a1540f33 100644
--- a/preferences/templates/preferences/edit_preferences.html
+++ b/preferences/templates/preferences/edit_preferences.html
@@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
diff --git a/preferences/templates/preferences/preferences.html b/preferences/templates/preferences/preferences.html
index 447df487..8ae9b6bc 100644
--- a/preferences/templates/preferences/preferences.html
+++ b/preferences/templates/preferences/preferences.html
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %}
{% load i18n %}
+{% load massive_bootstrap_form %}
{% block title %}{% trans "Preferences" %}{% endblock %}
@@ -37,7 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
diff --git a/preferences/urls.py b/preferences/urls.py
index a89fcdf3..89d0254c 100644
--- a/preferences/urls.py
+++ b/preferences/urls.py
@@ -80,5 +80,26 @@ urlpatterns = [
name='edit-mailcontact'
),
url(r'^del_mailcontact/$', views.del_mailcontact, name='del-mailcontact'),
+ url(r'^add_reminder/$', views.add_reminder, name='add-reminder'),
+ url(
+ r'^edit_reminder/(?P[0-9]+)$',
+ views.edit_reminder,
+ name='edit-reminder'
+ ),
+ url(r'^del_reminder/$', views.del_reminder, name='del-reminder'),
+ url(r'^add_radiuskey/$', views.add_radiuskey, name='add-radiuskey'),
+ url(
+ r'^edit_radiuskey/(?P[0-9]+)$',
+ views.edit_radiuskey,
+ name='edit-radiuskey'
+ ),
+ url(r'^del_radiuskey/(?P[0-9]+)$', views.del_radiuskey, name='del-radiuskey'),
+ url(r'^add_switchmanagementcred/$', views.add_switchmanagementcred, name='add-switchmanagementcred'),
+ url(
+ r'^edit_switchmanagementcred/(?P[0-9]+)$',
+ views.edit_switchmanagementcred,
+ name='edit-switchmanagementcred'
+ ),
+ url(r'^del_switchmanagementcred/(?P[0-9]+)$', views.del_switchmanagementcred, name='del-switchmanagementcred'),
url(r'^$', views.display_options, name='display-options'),
]
diff --git a/preferences/views.py b/preferences/views.py
index 559cdfef..e9f10d53 100644
--- a/preferences/views.py
+++ b/preferences/views.py
@@ -41,10 +41,14 @@ from django.utils.translation import ugettext as _
from reversion import revisions as reversion
from re2o.views import form
-from re2o.acl import can_create, can_edit, can_delete_set, can_view_all
+from re2o.acl import can_create, can_edit, can_delete_set, can_view_all, can_delete
+from .forms import MailContactForm, DelMailContactForm
from .forms import (
- ServiceForm, DelServiceForm, MailContactForm, DelMailContactForm
+ ServiceForm,
+ ReminderForm,
+ RadiusKeyForm,
+ SwitchManagementCredForm
)
from .models import (
Service,
@@ -55,7 +59,10 @@ from .models import (
MailMessageOption,
GeneralOption,
OptionalTopologie,
- HomeOption
+ HomeOption,
+ Reminder,
+ RadiusKey,
+ SwitchManagementCred
)
from . import models
from . import forms
@@ -76,6 +83,9 @@ def display_options(request):
mailmessageoptions, _created = MailMessageOption.objects.get_or_create()
service_list = Service.objects.all()
mailcontact_list = MailContact.objects.all()
+ reminder_list = Reminder.objects.all()
+ radiuskey_list = RadiusKey.objects.all()
+ switchmanagementcred_list = SwitchManagementCred.objects.all()
return form({
'useroptions': useroptions,
'machineoptions': machineoptions,
@@ -85,7 +95,10 @@ def display_options(request):
'homeoptions': homeoptions,
'mailmessageoptions': mailmessageoptions,
'service_list': service_list,
- 'mailcontact_list': mailcontact_list
+ 'mailcontact_list': mailcontact_list,
+ 'reminder_list': reminder_list,
+ 'radiuskey_list' : radiuskey_list,
+ 'switchmanagementcred_list': switchmanagementcred_list,
}, 'preferences/display_preferences.html', request)
@@ -196,6 +209,164 @@ def del_service(request, instances):
request
)
+@login_required
+@can_create(Reminder)
+def add_reminder(request):
+ """Ajout d'un service de la page d'accueil"""
+ reminder = ReminderForm(request.POST or None, request.FILES or None)
+ if service.is_valid():
+ with transaction.atomic(), reversion.create_revision():
+ reminder.save()
+ reversion.set_user(request.user)
+ reversion.set_comment("Creation")
+ messages.success(request, _("The service was added."))
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': service, 'action_name': _("Add a service")},
+ 'preferences/preferences.html',
+ request
+ )
+
+@login_required
+@can_edit(Reminder)
+def edit_reminder(request, service_instance, **_kwargs):
+ """Edition des services affichés sur la page d'accueil"""
+ reminder = ReminderForm(
+ request.POST or None,
+ request.FILES or None,
+ instance=reminder_instance
+ )
+ if reminder.is_valid():
+ with transaction.atomic(), reversion.create_revision():
+ reminder.save()
+ reversion.set_user(request.user)
+ reversion.set_comment(
+ "Field(s) edited: %s" % ', '.join(
+ field for field in reminder.changed_data
+ )
+ )
+ messages.success(request, _("The service was edited."))
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': service, 'action_name': _("Edit")},
+ 'preferences/preferences.html',
+ request
+ )
+
+
+
+@login_required
+@can_delete(Reminder)
+def del_reminder(request, reminder_instance, **_kwargs):
+ """Destruction d'un reminder"""
+ if request.method == "POST":
+ reminder_instance.delete()
+ messages.success(request, "Le reminder a été détruit")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'objet': reminder_instance, 'objet_name': 'reminder'},
+ 'preferences/delete.html',
+ request
+ )
+
+
+@login_required
+@can_create(RadiusKey)
+def add_radiuskey(request):
+ """Ajout d'une clef radius"""
+ radiuskey = RadiusKeyForm(request.POST or None)
+ if radiuskey.is_valid():
+ radiuskey.save()
+ messages.success(request, "Cette clef a été ajouté")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': radiuskey, 'action_name': 'Ajouter'},
+ 'preferences/preferences.html',
+ request
+ )
+
+@can_edit(RadiusKey)
+def edit_radiuskey(request, radiuskey_instance, **_kwargs):
+ """Edition des clefs radius"""
+ radiuskey = RadiusKeyForm(request.POST or None, instance=radiuskey_instance)
+ if radiuskey.is_valid():
+ radiuskey.save()
+ messages.success(request, "Radiuskey modifié")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': radiuskey, 'action_name': 'Editer'},
+ 'preferences/preferences.html',
+ request
+ )
+
+
+@login_required
+@can_delete(RadiusKey)
+def del_radiuskey(request, radiuskey_instance, **_kwargs):
+ """Destruction d'un radiuskey"""
+ if request.method == "POST":
+ try:
+ radiuskey_instance.delete()
+ messages.success(request, "La radiuskey a été détruite")
+ except ProtectedError:
+ messages.error(request, "Erreur la\
+ clef ne peut être supprimé, elle est affectée à des switchs")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'objet': radiuskey_instance, 'objet_name': 'radiuskey'},
+ 'preferences/delete.html',
+ request
+ )
+
+
+@login_required
+@can_create(SwitchManagementCred)
+def add_switchmanagementcred(request):
+ """Ajout de creds de management"""
+ switchmanagementcred = SwitchManagementCredForm(request.POST or None)
+ if switchmanagementcred.is_valid():
+ switchmanagementcred.save()
+ messages.success(request, "Ces creds ont été ajoutés")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': switchmanagementcred, 'action_name': 'Ajouter'},
+ 'preferences/preferences.html',
+ request
+ )
+
+@can_edit(SwitchManagementCred)
+def edit_switchmanagementcred(request, switchmanagementcred_instance, **_kwargs):
+ """Edition des creds de management"""
+ switchmanagementcred = SwitchManagementCredForm(request.POST or None, instance=switchmanagementcred_instance)
+ if switchmanagementcred.is_valid():
+ switchmanagementcred.save()
+ messages.success(request, "Creds de managament modifié")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'preferenceform': switchmanagementcred, 'action_name': 'Editer'},
+ 'preferences/preferences.html',
+ request
+ )
+
+
+@login_required
+@can_delete(SwitchManagementCred)
+def del_switchmanagementcred(request, switchmanagementcred_instance, **_kwargs):
+ """Destruction d'un switchmanagementcred"""
+ if request.method == "POST":
+ try:
+ switchmanagementcred_instance.delete()
+ messages.success(request, "Ces creds ont été détruits")
+ except ProtectedError:
+ messages.error(request, "Erreur ces\
+ creds ne peuvent être supprimés, ils sont affectés à des switchs")
+ return redirect(reverse('preferences:display-options'))
+ return form(
+ {'objet': switchmanagementcred_instance, 'objet_name': 'switchmanagementcred'},
+ 'preferences/delete.html',
+ request
+ )
+
@login_required
@can_create(MailContact)
diff --git a/topologie/migrations/0063_auto_20180919_2225.py b/topologie/migrations/0063_auto_20180919_2225.py
new file mode 100644
index 00000000..45228340
--- /dev/null
+++ b/topologie/migrations/0063_auto_20180919_2225.py
@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-09-19 20:25
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('preferences', '0051_auto_20180919_2225'),
+ ('topologie', '0062_auto_20180815_1918'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='modelswitch',
+ name='firmware',
+ field=models.CharField(blank=True, max_length=255, null=True),
+ ),
+ migrations.AddField(
+ model_name='switch',
+ name='management_creds',
+ field=models.ForeignKey(blank=True, help_text='Identifiant de management de ce switch', null=True, on_delete=django.db.models.deletion.PROTECT, to='preferences.SwitchManagementCred'),
+ ),
+ migrations.AddField(
+ model_name='switch',
+ name='radius_key',
+ field=models.ForeignKey(blank=True, help_text='Clef radius du switch', null=True, on_delete=django.db.models.deletion.PROTECT, to='preferences.RadiusKey'),
+ ),
+ ]
diff --git a/topologie/migrations/0064_switch_automatic_provision.py b/topologie/migrations/0064_switch_automatic_provision.py
new file mode 100644
index 00000000..d8d280cf
--- /dev/null
+++ b/topologie/migrations/0064_switch_automatic_provision.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2018-09-20 16:28
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('topologie', '0063_auto_20180919_2225'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='switch',
+ name='automatic_provision',
+ field=models.BooleanField(default=False, help_text='Provision automatique de ce switch'),
+ ),
+ ]
diff --git a/topologie/models.py b/topologie/models.py
index b63bece6..d02fce40 100644
--- a/topologie/models.py
+++ b/topologie/models.py
@@ -49,6 +49,11 @@ from django.db import transaction
from django.utils.translation import ugettext_lazy as _
from reversion import revisions as reversion
+from preferences.models import (
+ OptionalTopologie,
+ RadiusKey,
+ SwitchManagementCred
+)
from machines.models import Machine, regen
from re2o.mixins import AclMixin, RevMixin
@@ -228,6 +233,24 @@ class Switch(AclMixin, Machine):
null=True,
on_delete=models.SET_NULL,
)
+ radius_key = models.ForeignKey(
+ 'preferences.RadiusKey',
+ blank=True,
+ null=True,
+ on_delete=models.PROTECT,
+ help_text="Clef radius du switch"
+ )
+ management_creds = models.ForeignKey(
+ 'preferences.SwitchManagementCred',
+ blank=True,
+ null=True,
+ on_delete=models.PROTECT,
+ help_text="Identifiant de management de ce switch"
+ )
+ automatic_provision = models.BooleanField(
+ default=False,
+ help_text='Provision automatique de ce switch',
+ )
class Meta:
unique_together = ('stack', 'stack_member_id')
@@ -285,13 +308,78 @@ class Switch(AclMixin, Machine):
ValidationError(_("Creation of an existing port."))
def main_interface(self):
- """ Returns the 'main' interface of the switch """
+ """ Returns the 'main' interface of the switch
+ It must the the management interface for that device"""
+ switch_iptype = OptionalTopologie.get_cached_value('switchs_ip_type')
+ if switch_iptype:
+ return self.interface_set.filter(type__ip_type=switch_iptype).first()
return self.interface_set.first()
@cached_property
def get_name(self):
return self.name or self.main_interface().domain.name
+ @cached_property
+ def get_radius_key(self):
+ """Retourne l'objet de la clef radius de ce switch"""
+ return self.radius_key or RadiusKey.objects.filter(default_switch=True).first()
+
+ @cached_property
+ def get_radius_key_value(self):
+ """Retourne la valeur en str de la clef radius, none si il n'y en a pas"""
+ if self.get_radius_key:
+ return self.get_radius_key.radius_key
+ else:
+ return None
+
+ @cached_property
+ def get_management_cred(self):
+ """Retourne l'objet des creds de managament de ce switch"""
+ return self.management_creds or SwitchManagementCred.objects.filter(default_switch=True).first()
+
+ @cached_property
+ def get_management_cred_value(self):
+ """Retourne un dict des creds de management du switch"""
+ if self.get_management_cred:
+ return {'id': self.get_management_cred.management_id, 'pass': self.get_management_cred.management_pass}
+ else:
+ return None
+
+ @cached_property
+ def rest_enabled(self):
+ return OptionalTopologie.get_cached_value('switchs_rest_management') or self.automatic_provision
+
+ @cached_property
+ def web_management_enabled(self):
+ sw_management = OptionalTopologie.get_cached_value('switchs_web_management')
+ sw_management_ssl = OptionalTopologie.get_cached_value('switchs_web_management_ssl')
+ if sw_management_ssl:
+ return "ssl"
+ elif sw_management:
+ return "plain"
+ else:
+ return self.automatic_provision
+
+ @cached_property
+ def ipv4(self):
+ """Return the switch's management ipv4"""
+ return str(self.main_interface().ipv4)
+
+ @cached_property
+ def ipv6(self):
+ """Returne the switch's management ipv6"""
+ return str(self.main_interface().ipv6().first())
+
+ @cached_property
+ def interfaces_subnet(self):
+ """Return dict ip:subnet for all ip of the switch"""
+ return dict((str(interface.ipv4), interface.type.ip_type.ip_set_full_info) for interface in self.interface_set.all())
+
+ @cached_property
+ def interfaces6_subnet(self):
+ """Return dict ip6:subnet for all ipv6 of the switch"""
+ return dict((str(interface.ipv6().first()), interface.type.ip_type.ip6_set_full_info) for interface in self.interface_set.all())
+
def __str__(self):
return str(self.get_name)
@@ -304,6 +392,11 @@ class ModelSwitch(AclMixin, RevMixin, models.Model):
'topologie.ConstructorSwitch',
on_delete=models.PROTECT
)
+ firmware = models.CharField(
+ max_length=255,
+ null=True,
+ blank=True
+ )
class Meta:
permissions = (
@@ -437,8 +530,20 @@ class Port(AclMixin, RevMixin, models.Model):
verbose_name_plural = _("ports")
@cached_property
- def get_port_profile(self):
- """Return the config profile for this port
+ def pretty_name(self):
+ """More elaborated name for label on switch conf"""
+ if self.related:
+ return "Uplink : " + self.related.switch.short_name
+ elif self.machine_interface:
+ return "Machine : " + str(self.machine_interface.domain)
+ elif self.room:
+ return "Chambre : " + str(self.room)
+ else:
+ return "Inconnue"
+
+ @cached_property
+ def get_port_profil(self):
+ """Return the config profil for this port
:returns: the profile of self (port)"""
def profile_or_nothing(profile):
port_profile = PortProfile.objects.filter(
@@ -447,7 +552,7 @@ class Port(AclMixin, RevMixin, models.Model):
return port_profile
else:
nothing_profile, _created = PortProfile.objects.get_or_create(
- profile_default='nothing',
+ profil_default='nothing',
name='nothing',
radius_type='NO'
)
diff --git a/topologie/templates/topologie/aff_model_switch.html b/topologie/templates/topologie/aff_model_switch.html
index 6b3e7156..fc49331f 100644
--- a/topologie/templates/topologie/aff_model_switch.html
+++ b/topologie/templates/topologie/aff_model_switch.html
@@ -35,15 +35,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% trans "Reference" as tr_ref %}
{% include "buttons/sort.html" with prefix='model-switch' col='reference' text=tr_ref %} |
+ Firmware |
{% trans "Switch constructor" as tr_constructor %}
{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text=tr_constructor %} |
- |
+ {% trans "Switches" %}
+ | |
{% for model_switch in model_switch_list %}
{{ model_switch.reference }} |
+ {{model_switch.firmware}} |
{{ model_switch.constructor }} |
+
+ {% for switch in model_switch.switch_set.all %}
+
+ {{ switch }}
+
+ {% endfor %}
+ |
{% can_edit model_switch %}
@@ -64,4 +74,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if model_switch_list.paginator %}
{% include "pagination.html" with list=model_switch_list %}
{% endif %}
+
+
diff --git a/topologie/templates/topologie/aff_vlanoptions.html b/topologie/templates/topologie/aff_vlanoptions.html
new file mode 100644
index 00000000..3810cb7f
--- /dev/null
+++ b/topologie/templates/topologie/aff_vlanoptions.html
@@ -0,0 +1,60 @@
+{% 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 acl %}
+{% load logs_extra %}
+
+
+
+
+
+ Id |
+ Nom |
+ Arp Protect |
+ Dhcp Snooping |
+ Dhcpv6 Snooping |
+ Igmp |
+ Mld |
+ |
+
+
+ {% for vlan in vlan_list %}
+
+ {{ vlan.vlan_id }} |
+ {{ vlan.name }} |
+ {{ vlan.arp_protect }} |
+ {{ vlan.dhcp_snooping }} |
+ {{ vlan.dhcpv6_snooping }} |
+ {{ vlan.igmp }} |
+ {{ vlan.mld }} |
+
+ {% can_edit vlan %}
+ {% include 'buttons/edit.html' with href='topologie:edit-vlanoptions' id=vlan.id %}
+ {% acl_end %}
+ {% history_button vlan %}
+ |
+
+ {% endfor %}
+
+
diff --git a/topologie/templates/topologie/index_portprofile.html b/topologie/templates/topologie/index_portprofile.html
index 730823f6..4a603210 100644
--- a/topologie/templates/topologie/index_portprofile.html
+++ b/topologie/templates/topologie/index_portprofile.html
@@ -30,12 +30,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
+
{% trans "Port profiles" %}
{% can_create PortProfile %}
{% trans " Add a port profile" %}
{% acl_end %}
{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %}
+
+
+{% trans "Sécurité par vlan" %}
+{% include "topologie/aff_vlanoptions.html" with vlan_list=vlan_list %}
+
diff --git a/topologie/urls.py b/topologie/urls.py
index c314c800..77d68d50 100644
--- a/topologie/urls.py
+++ b/topologie/urls.py
@@ -120,4 +120,7 @@ urlpatterns = [
url(r'^del_port_profile/(?P[0-9]+)$',
views.del_port_profile,
name='del-port-profile'),
-]
+ url(r'^edit_vlanoptions/(?P[0-9]+)$',
+ views.edit_vlanoptions,
+ name='edit-vlanoptions'),
+ ]
diff --git a/topologie/views.py b/topologie/views.py
index 3d8a3044..74ea1148 100644
--- a/topologie/views.py
+++ b/topologie/views.py
@@ -60,10 +60,15 @@ from re2o.settings import MEDIA_ROOT
from machines.forms import (
DomainForm,
EditInterfaceForm,
- AddInterfaceForm
+ AddInterfaceForm,
+ EditOptionVlanForm
)
from machines.views import generate_ipv4_mbf_param
-from machines.models import Interface, Service_link
+from machines.models import (
+ Interface,
+ Service_link,
+ Vlan
+)
from preferences.models import AssoOption, GeneralOption
from .models import (
@@ -153,10 +158,11 @@ def index_port_profile(request):
'vlan_untagged')
port_profile_list = re2o_paginator(
request, port_profile_list, pagination_number)
+ vlan_list = Vlan.objects.all().order_by('vlan_id')
return render(
request,
'topologie/index_portprofile.html',
- {'port_profile_list': port_profile_list}
+ {'port_profile_list': port_profile_list, 'vlan_list': vlan_list}
)
@@ -307,6 +313,23 @@ def index_model_switch(request):
)
+@login_required
+@can_edit(Vlan)
+def edit_vlanoptions(request, vlan_instance, **_kwargs):
+ """ View used to edit options for switch of VLAN object """
+ vlan = EditOptionVlanForm(request.POST or None, instance=vlan_instance)
+ if vlan.is_valid():
+ if vlan.changed_data:
+ vlan.save()
+ messages.success(request, "Vlan modifié")
+ return redirect(reverse('topologie:index-port-profile'))
+ return form(
+ {'vlanform': vlan, 'action_name': 'Editer'},
+ 'machines/machine.html',
+ request
+ )
+
+
@login_required
@can_create(Port)
def new_port(request, switchid):
|