8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-22 15:03:45 +00:00

Merge branch 'switch_conf' into 'dev'

Switch conf

See merge request federez/re2o!308
This commit is contained in:
grizzly 2018-09-27 16:34:59 +02:00
commit 8045645502
26 changed files with 1315 additions and 28 deletions

View file

@ -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

View file

@ -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),

View file

@ -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

View file

@ -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(

View file

@ -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'),
),
]

View file

@ -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)

View file

@ -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)

View file

@ -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__'

View file

@ -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'),
),
]

View file

@ -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 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 = (

View file

@ -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 %}
<table class="table table-striped">
<thead>
<tr>
<th>Id Clef</th>
<th>Commentaire</th>
<th>Clef par default des switchs</th>
<th>Clef utilisée par les switchs</th>
<th></th>
<th></th>
</tr>
</thead>
{% for radiuskey in radiuskey_list %}
<tr>
<td>{{ radiuskey.id }}</td>
<td>{{ radiuskey.comment }}</td>
<td>{{ radiuskey.default_switch }}</td>
<td>{{ radiuskey.switch_set.all|join:", " }}</td>
<td class="text-right">
{% can_edit radiuskey %}
{% include 'buttons/edit.html' with href='preferences:edit-radiuskey' id=radiuskey.id %}
{% acl_end %}
{% can_delete radiuskey %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-radiuskey' radiuskey.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
{% history_button radiuskey %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -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 %}
<table class="table table-striped">
<thead>
<tr>
<th>Identifiant</th>
<th>Creds par default des switchs</th>
<th>Utilisé pour les switchs</th>
<th></th>
<th></th>
</tr>
</thead>
{% for switchmanagementcred in switchmanagementcred_list %}
<tr>
<td>{{ switchmanagementcred.management_id }}</td>
<td>{{ switchmanagementcred.default_switch }}</td>
<td>{{ switchmanagementcred.switch_set.all|join:", " }}</td>
<td class="text-right">
{% can_edit switchmanagementcred %}
{% include 'buttons/edit.html' with href='preferences:edit-switchmanagementcred' id=switchmanagementcred.id %}
{% acl_end %}
{% can_delete switchmanagementcred %}
<a class="btn btn-danger btn-sm" role="button" title="Supprimer" href="{% url 'preferences:del-switchmanagementcred' switchmanagementcred.pk %}">
<i class="fa fa-trash"></i>
</a>
{% acl_end %}
{% history_button switchmanagementcred %}
</td>
</tr>
{% endfor %}
</table>

View file

@ -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 %}
<form class="form" method="post">
{% csrf_token %}
<h4>Attention, voulez-vous vraiment supprimer cet objet {{ objet_name }} ( {{ objet }} ) ?</h4>
{% bootstrap_button "Confirmer" button_type="submit" icon="trash" %}
</form>
<br />
<br />
<br />
{% endblock %}

View file

@ -118,6 +118,75 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<th>{% trans "VLAN for machines rejected by RADIUS" %}</th>
<td>{{ topologieoptions.vlan_decision_nok }}</td>
</tr>
<tr>
<th>Placement sur ce vlan par default en cas de rejet</th>
<td>{{ topologieoptions.vlan_decision_nok }}</td>
</tr>
</table>
<h6>Clef radius</h6>
{% can_create RadiusKey%}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-radiuskey' %}"><i class="fa fa-plus"></i> Ajouter une clef radius</a>
{% acl_end %}
{% include "preferences/aff_radiuskey.html" with radiuskey_list=radiuskey_list %}
<h4>Configuration des switches</h4>
<table class="table table-striped">
<tr>
<th>Web management, activé si provision automatique</th>
<td>{{ topologieoptions.switchs_web_management }}</td>
<th>Rest management, activé si provision auto</th>
<td>{{ topologieoptions.switchs_rest_management }}</td>
</tr>
</table>
<h5>{% if topologieoptions.provision_switchs_enabled %}<span class="label label-success">Provision de la config des switchs{% else %}<span class="label label-danger">Provision de la config des switchs{% endif%}</span></h5>
<table class="table table-striped">
<tr>
<th>Switchs configurés automatiquement</th>
<td>{{ topologieoptions.provisioned_switchs|join:", " }} {% if topologieoptions.provisioned_switchs %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
</tr>
<tr>
<th>Plage d'ip de management des switchs</th>
<td>{{ topologieoptions.switchs_ip_type }} {% if topologieoptions.switchs_ip_type %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
</tr>
<tr>
<th>Serveur des config des switchs</th>
<td>{{ topologieoptions.switchs_management_interface }} {% if topologieoptions.switchs_management_interface %} - {{ topologieoptions.switchs_management_interface_ip }} <span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span></td>
</tr>
<tr>
<th>Mode de provision des switchs</th>
<td>{{ topologieoptions.switchs_provision }}</td>
</tr>
<tr>
<th>Mode TFTP</th>
<td><span class="label label-success"> OK</span></td>
</tr>
<tr>
<th>Mode SFTP</th>
<td>{% if topologieoptions.switchs_management_sftp_creds %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Creds manquants{% endif %}</span></td>
</tr>
</table>
<h6>Creds de management des switchs</h6>
{% can_create SwitchManagementCred%}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:add-switchmanagementcred' %}"><i class="fa fa-plus"></i> Ajouter un id/mdp de management switch</a>
{% acl_end %}
<p>
</p>
{% if switchmanagementcred_list %}<span class="label label-success"> OK{% else %}<span class="label label-danger">Manquant{% endif %}</span>
{% include "preferences/aff_switchmanagementcred.html" with switchmanagementcred_list=switchmanagementcred_list %}
<h5>{% if topologieoptions.provisioned_switchs %}<span class="label label-success">Provision de la config des switchs{% else %}<span class="label label-danger">Provision de la config des switchs{% endif%}</span></h5>
<table class="table table-striped">
<tr>
<th>Switchs configurés automatiquement</th>
<td>{{ topologieoptions.provisioned_switchs|join:", " }}</td>
</tr>
</table>
<h4>{% trans "General preferences" %}</h4>
<a class="btn btn-primary btn-sm" role="button" href="{% url 'preferences:edit-options' 'GeneralOption' %}">

View file

@ -36,7 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<form class="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% massive_bootstrap_form options 'utilisateur_asso' %}
{% massive_bootstrap_form options 'utilisateur_asso,automatic_provision_switchs' %}
{% trans "Edit" as tr_edit %}
{% bootstrap_button tr_edit button_type="submit" icon='ok' button_class='btn-success' %}
</form>

View file

@ -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.,
<form class="form" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% if preferenceform %}
{% bootstrap_form preferenceform %}
{% massive_bootstrap_form preferenceform 'members' %}
{% endif %}
{% bootstrap_button action_name button_type="submit" icon='ok' button_class='btn-success' %}
</form>

View file

@ -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<reminderid>[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<radiuskeyid>[0-9]+)$',
views.edit_radiuskey,
name='edit-radiuskey'
),
url(r'^del_radiuskey/(?P<radiuskeyid>[0-9]+)$', views.del_radiuskey, name='del-radiuskey'),
url(r'^add_switchmanagementcred/$', views.add_switchmanagementcred, name='add-switchmanagementcred'),
url(
r'^edit_switchmanagementcred/(?P<switchmanagementcredid>[0-9]+)$',
views.edit_switchmanagementcred,
name='edit-switchmanagementcred'
),
url(r'^del_switchmanagementcred/(?P<switchmanagementcredid>[0-9]+)$', views.del_switchmanagementcred, name='del-switchmanagementcred'),
url(r'^$', views.display_options, name='display-options'),
]

View file

@ -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)

View file

@ -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'),
),
]

View file

@ -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'),
),
]

View file

@ -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'
)

View file

@ -35,15 +35,25 @@ with this program; if not, write to the Free Software Foundation, Inc.,
<tr>
{% trans "Reference" as tr_ref %}
<th>{% include "buttons/sort.html" with prefix='model-switch' col='reference' text=tr_ref %}</th>
<th>Firmware</th>
{% trans "Switch constructor" as tr_constructor %}
<th>{% include "buttons/sort.html" with prefix='model-switch' col='constructor' text=tr_constructor %}</th>
<th></th>
<th>{% trans "Switches" %}
<th></th>
</tr>
</thead>
{% for model_switch in model_switch_list %}
<tr>
<td>{{ model_switch.reference }}</td>
<td>{{model_switch.firmware}}</td>
<td>{{ model_switch.constructor }}</td>
<td>
{% for switch in model_switch.switch_set.all %}
<a href="{% url 'topologie:index-port' switch.pk %}">
{{ switch }}
</a>
{% endfor %}
</td>
<td class="text-right">
{% can_edit model_switch %}
<a class="btn btn-primary btn-sm" role="button" title={% trans "Edit" %} href="{% url 'topologie:edit-model-switch' model_switch.id %}">
@ -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 %}

View file

@ -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 %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Id</th>
<th>Nom</th>
<th>Arp Protect</th>
<th>Dhcp Snooping</th>
<th>Dhcpv6 Snooping</th>
<th>Igmp</th>
<th>Mld</th>
<th></th>
</tr>
</thead>
{% for vlan in vlan_list %}
<tr>
<td>{{ vlan.vlan_id }}</td>
<td>{{ vlan.name }}</td>
<td>{{ vlan.arp_protect }}</td>
<td>{{ vlan.dhcp_snooping }}</td>
<td>{{ vlan.dhcpv6_snooping }}</td>
<td>{{ vlan.igmp }}</td>
<td>{{ vlan.mld }}</td>
<td class="text-right">
{% can_edit vlan %}
{% include 'buttons/edit.html' with href='topologie:edit-vlanoptions' id=vlan.id %}
{% acl_end %}
{% history_button vlan %}
</td>
</tr>
{% endfor %}
</table>
</div>

View file

@ -30,12 +30,18 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% block content %}
<h2>{% trans "Port profiles" %}</h2>
{% can_create PortProfile %}
<a class="btn btn-primary btn-sm" role="button" href="{% url 'topologie:new-port-profile' %}"><i class="fa fa-plus"></i>{% trans " Add a port profile" %}</a>
<hr>
{% acl_end %}
{% include "topologie/aff_port_profile.html" with port_profile_list=port_profile_list %}
<h2>{% trans "Sécurité par vlan" %}</h2>
{% include "topologie/aff_vlanoptions.html" with vlan_list=vlan_list %}
<br />
<br />
<br />

View file

@ -120,4 +120,7 @@ urlpatterns = [
url(r'^del_port_profile/(?P<portprofileid>[0-9]+)$',
views.del_port_profile,
name='del-port-profile'),
]
url(r'^edit_vlanoptions/(?P<vlanid>[0-9]+)$',
views.edit_vlanoptions,
name='edit-vlanoptions'),
]

View file

@ -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):