Changeset - b02e16dc7708
[Not reviewed]
default
0 2 0
Branko Majic (branko) - 12 years ago 2013-11-09 14:02:29
branko@majic.rs
CONNT-17: Added tabelarised representation of communications to the project details page with colour-coding.
2 files changed with 27 insertions and 0 deletions:
0 comments (0 inline, 0 general)
conntrackt/templates/conntrackt/project_detail.html
Show inline comments
 
{% extends "conntrackt/base.html" %}
 

	
 
{# For html_link #}
 
{% load conntrackt_tags %}
 

	
 
{% block content %}
 
<div class="row">
 
  <h1 class="span12">{{project.name}}</h1>
 
</div>
 
<hr>
 

	
 
{% if project.description %}
 
<div class="row">
 
  <div class="span12">
 
    {{project.description}}
 
  </div>
 
</div>
 
<hr>
 
{% endif %}
 

	
 
<div class="row">
 
  <div class="span12">
 
    {% html_link "Edit" "project_update" project.id class="btn btn-primary" %}
 
    {% html_link "Remove" "project_delete" project.id class="btn btn-primary" %}
 
    {% with project_id=project.id|slugify %}
 
    {% html_link "Add entity" "entity_create" class="btn btn-primary" get="project="|add:project_id|add:"&next="|add:request.path %}
 
    {% html_link "Add communication" "communication_create" class="btn btn-primary" get="project="|add:project_id|add:"&next="|add:request.path %}
 
    {% endwith %}
 
    {% html_link "Get Iptables" 'project_iptables' project.id class="btn btn-primary" %}
 
  </div>
 
</div>
 
<hr>
 
{% if location_entities %}
 
<div class="row">
 
  {% for location, entities in location_entities %}
 
  <div class="span4">
 
    <div class="well">{% include "conntrackt/location_widget.html" with location=location entities=entities %}</div>
 
  </div>
 
  {% endfor %}
 
</div>
 

	
 
{% if communications %}
 
<div class="row">
 
  <div class="span12">
 
    <h2>Communications</h2>
 
    <table class="table table-hover table-condensed table-striped">
 
      <thead>
 
        <tr><th>From</th><th>To</th><th>Protocol</th><th>Port</th></tr>
 
      </thead>
 
      <tbody>
 
      {% for communication in communications %}
 
        <tr>
 
          <td><span class="badge" style="background-color: {{ communication.source_color }};">O</span> {{ communication.source }}</td>
 
          <td><span class="badge" style="background-color: {{ communication.destination_color }};">O</span> {{ communication.destination }}</td>
 
          <td>{{ communication.protocol }}</td>
 
          <td>{{ communication.port }}</td>
 
        </tr>
 
      {% endfor %}
 
      </tbody>
 
    </table>
 
  </div>
 
</div>
 
{% endif %}
 

	
 
<div class="row">
 
  <div class="span12">
 
    <h2>Communications diagram</h2>
 
    <img src="{% url "project_diagram" project.id %}" width="100%">
 
  </div>
 
</div>
 
{% endif %}
 
{% endblock %}
 

	
conntrackt/views.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
#
 
# Copyright (C) 2013 Branko Majic
 
#
 
# This file is part of Django Conntrackt.
 
#
 
# Django Conntrackt 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 3 of the License, or (at your option) any
 
# later version.
 
#
 
# Django Conntrackt 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
 
# Django Conntrackt.  If not, see <http://www.gnu.org/licenses/>.
 
#
 

	
 

	
 
# Standard library imports.
 
from StringIO import StringIO
 
from zipfile import ZipFile, ZIP_DEFLATED
 
import json
 

	
 
# Django imports.
 
from django.contrib.auth.decorators import permission_required
 
from django.contrib import messages
 
from django.core.exceptions import ValidationError
 
from django.core.urlresolvers import reverse, reverse_lazy
 
from django.db.models import Q
 
from django.db.models.deletion import Collector
 
from django.http import HttpResponse
 
from django.shortcuts import render_to_response, get_object_or_404
 
from django.views.generic import TemplateView, DetailView, CreateView, UpdateView, DeleteView, View
 

	
 
# Third-party application imports.
 
from braces.views import MultiplePermissionsRequiredMixin, SetHeadlineMixin
 

	
 
# Application imports.
 
from .forms import ProjectForm, LocationForm, EntityForm, InterfaceForm, CommunicationForm
 
from .models import Project, Entity, Location, Interface, Communication
 
from .utils import generate_entity_iptables, generate_project_diagram
 

	
 

	
 
class RedirectToNextMixin(object):
 
    """
 
    View mixin that can be used for redirecting the user to URL defined through
 
    a GET parameter. The mixin is usable with Create/Update/Delete views that
 
    utilise the get_success_url() call.
 

	
 
    The mixin accepts the following class options:
 

	
 
        next_parameter - Name of the GET parameter that contains the redirect
 
        URL. Defaults to "next".
 
    """
 

	
 
    next_parameter = "next"
 

	
 
    def get_success_url(self):
 
        """
 
        Returns the success URL to which the user will be redirected.
 
        """
 

	
 
        return self.request.GET.get(self.next_parameter, super(RedirectToNextMixin, self).get_success_url())
 

	
 

	
 
class RelatedItemsMixin(object):
 
    """
 
    View mixin that adds related items of a referenced model object to context
 
    data in form of a nested list, including the reference model object as the
 
    first element of a list.
 

	
 
    The context data will be passed using the "related_items" key.
 

	
 
    This data can be used in the template by passing it through the
 
    unordered_list template tag.
 

	
 
    The reference object is accessed via "object" property of calling view
 
    (i.e. self.object).
 

	
 
    For more details on implementation, see:
 

	
 
    RelatedCollectorMixin.get_dependant_objects_representation method
 
    """
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Adds the related items of a reference model object to context data.
 

	
 
        Returns:
 
          Context data.
 
        """
 

	
 
        # Set the context using the parent class.
 
        context = super(RelatedItemsMixin, self).get_context_data(**kwargs)
 

	
 
        # Add to context the nested list of string representations of related
 
        # items.
 
        context["related_items"] = self.object.get_dependant_objects_representation()
 

	
 
        return context
 

	
 

	
 
class IndexView(MultiplePermissionsRequiredMixin, TemplateView):
 
    """
 
    Custom view used for rendering the index page.
 
    """
 

	
 
    template_name = 'conntrackt/index.html'
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.view",),
 
        }
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Returns the context data that should be used for rendering of the
 
        template.
 

	
 
        Adds the 'projects' context object containing all of the projects
 
        available sorted aplhabetically by name.
 
        """
 

	
 
        # Set the context using the parent class.
 
        context = super(IndexView, self).get_context_data(**kwargs)
 

	
 
        # Store information about all projcts in context.
 
        context['projects'] = Project.objects.all().order_by('name')
 

	
 
        # Store information about all locations in context.
 
        context['locations'] = Location.objects.all().order_by('name')
 

	
 
        return context
 

	
 

	
 
class ProjectView(MultiplePermissionsRequiredMixin, DetailView):
 
    """
 
    Custom view for presenting the project information.
 
    """
 

	
 
    model = Project
 

	
 
    permissions = {
 
        "all": ("conntrackt.view",),
 
        }
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Returns the context data that should be used for rendering of the
 
        template.
 

	
 
        Adds the 'location_entities' context object that contains tuples of the
 
        form '(location, entities)', where location is an instance of a
 
        Location, and entities is a query set of entities that belong to that
 
        particular location, and to related project.
 
        """
 

	
 
        # Set the context using the parent class.
 
        context = super(ProjectView, self).get_context_data(**kwargs)
 

	
 
        # Set-up an array that will contaion (location, entities) tuples.
 
        location_entities = []
 

	
 
        # Add the (location, entities) tuple for each location that has entities
 
        # belonging to the related project.
 
        for location in Location.objects.filter(entity__project=self.object).distinct().order_by("name"):
 
            entities = Entity.objects.filter(project=self.object, location=location)
 
            location_entities.append((location, entities))
 

	
 
        # Add the (location, entities) tuples to context.
 
        context['location_entities'] = location_entities
 

	
 
        # Add all project communications to context.
 
        context['communications'] = self.object.get_project_communications_summary()
 

	
 
        # Finally return the context.
 
        return context
 

	
 

	
 
class EntityView(MultiplePermissionsRequiredMixin, DetailView):
 
    """
 
    Custom view for presenting entity information.
 
    """
 

	
 
    # Optimise the query to fetch the related data from reverse relationships.
 
    queryset = Entity.objects.all()
 
    queryset = queryset.prefetch_related('interface_set__destination_set__source__entity')
 
    queryset = queryset.prefetch_related('interface_set__destination_set__destination__entity')
 
    queryset = queryset.prefetch_related('interface_set__source_set__source__entity')
 
    queryset = queryset.prefetch_related('interface_set__source_set__destination__entity')
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.view",),
 
        }
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_context_data(self, **kwargs):
 
        """
 
        Returns the context data that should be used for rendering of the
 
        template.
 

	
 
        Adds the 'entity_iptables' context object that contains full iptables
 
        rules generated for the entity.
 
        """
 

	
 
        # Call the parent class method.
 
        context = super(EntityView, self).get_context_data(**kwargs)
 

	
 
        # Add the rendered iptables rules to the context.
 
        context['entity_iptables'] = generate_entity_iptables(self.object)
 

	
 
        # Add the incoming and outgoing commmunication to the context.
 
        context["incoming_communications"] = self.object.incoming_communications()
 
        context["outgoing_communications"] = self.object.outgoing_communications()
 

	
 
        # Add the interfaces to the context.
 
        context["interfaces"] = self.object.interface_set.all().order_by("name")
 

	
 
        # Add project/location to the context.
 
        context["project"] = self.object.project
 
        context["location"] = self.object.location
 

	
 
        return context
 

	
 

	
 
@permission_required("conntrackt.view", raise_exception=True)
 
def entity_iptables(request, pk):
 
    """
 
    Custom view that returns response containing iptables rules generated for an
 
    entity.
 

	
 
    Makes sure to set the Content-Disposition of a response in order to
 
    signal the browser it should start download of this view's response
 
    immediately. Also sets the suggested filename for it.
 

	
 
    Arguments:
 

	
 
        pk - Primary key of the Entity object for which the rules should be
 
        generated.
 

	
 
    Returns:
 

	
 
        Response object that contains the iptables rules for specified entity.
 
    """
 

	
 
    # Fetch the entity, and construct the response with iptables rules as
 
    # content.
 
    entity = get_object_or_404(Entity, pk=pk)
 
    content = generate_entity_iptables(entity)
 
    response = HttpResponse(content, mimetype='text/plain')
 

	
 
    # Add the Content-Disposition information for the browser, telling the
 
    # browser to download the file with suggested filename.
 
    response['Content-Disposition'] = "attachment; filename=%s-iptables.conf" % entity.name.lower().replace(" ", "_")
 

	
 
    return response
 

	
 

	
 
@permission_required("conntrackt.view", raise_exception=True)
 
def project_iptables(request, project_id, location_id=None):
 
    """
 
    Custom view for obtaining iptables for all entities of a project or project
 
    location in a single ZIP file.
 

	
 
    Arguments:
 

	
 
        request - Request object.
 

	
 
        project_id - Unique ID of the project for whose entities the iptables
 
        rules should be generated.
 

	
 
        location_id - Optional unique ID of the project location for whose
 
        entities the iptables rules should be generated. Default is None, which
 
        means generate rules for _all_ entities in a project.
 

	
 
    Returns:
 

	
 
        Response object that contains the ZIP file and Content-Disposition
 
        information.
 
    """
 

	
 
    # Fetch the project.
 
    project = get_object_or_404(Project, pk=project_id)
 

	
 
    # Set-up a string IO object to which we'll write the ZIP file (in-memory).
 
    buff = StringIO()
 

	
 
    # Create a new ZIP file in-memory.
 
    zipped_iptables = ZipFile(buff, "w", ZIP_DEFLATED)
 

	
 
    # Create the response object, setting the mime type so browser could offer
 
    # to open the file with program as well.
 
    response = HttpResponse(mimetype='application/zip')
 

	
 
    # If specific location was specified, get the entities that are part of that
 
    # project location only, otherwise fetch all of the project's entities. Also
 
    # set-up the filename that will be suggested to the browser.
 
    if location_id:
 
        location = get_object_or_404(Location, pk=location_id)
 
        entities = project.entity_set.filter(location=location)
 
        filename = '%s-%s-iptables.zip' % (project.name.lower().replace(" ", "_"), location.name.lower().replace(" ", "_"))
 
    else:
 
        entities = project.entity_set.all()
 
        filename = '%s-iptables.zip' % (project.name.lower().replace(" ", "_"))
 

	
 
    # Render iptables rules for each entity, placing them in the ZIP archive.
 
    for entity in entities:
 
        entity_iptables = generate_entity_iptables(entity)
 
        zipped_iptables.writestr("%s-iptables.conf" % entity.name.lower().replace(" ", "_"), entity_iptables)
 

	
 
    # Close the archive, and flush the buffer.
 
    zipped_iptables.close()
 
    buff.flush()
 

	
 
    # Write the contents of our buffer (ZIP archive) to response content, and
 
    # close the IO string.
 
    response.write(buff.getvalue())
 
    buff.close()
 

	
 
    # Set the Content-Disposition so the browser would know it should download
 
    # the archive, and suggest the filename.
 
    response['Content-Disposition'] = 'attachment; filename="%s"' % filename
 

	
 
    # Finally return the response object.
 
    return response
 

	
 

	
 
class ProjectCreateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, CreateView):
 
    """
 
    View for creating a new project.
 
    """
 

	
 
    model = Project
 
    form_class = ProjectForm
 
    headline = "Add new project"
 
    template_name = "conntrackt/create_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.add_project",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 

	
 
class ProjectUpdateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, UpdateView):
 
    """
 
    View for modifying an existing project.
 
    """
 

	
 
    model = Project
 
    form_class = ProjectForm
 
    template_name = "conntrackt/update_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.change_project",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_headline(self):
 
        """
 
        Set headline based on project name.
 
        """
 

	
 
        return "Update project %s" % self.object.name
 

	
 

	
 
class ProjectDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting a project.
 
    """
 

	
 
    model = Project
 
    template_name = "conntrackt/delete_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.delete_project",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    success_url = reverse_lazy("index")
 

	
 
    def post(self, *args, **kwargs):
 
        """
 
        Add a success message that will be displayed to the user to confirm the
 
        project deletion.
 
        """
 

	
 
        messages.success(self.request, "Project %s has been removed." % self.get_object().name, extra_tags="alert alert-success")
 

	
 
        return super(ProjectDeleteView, self).post(*args, **kwargs)
 

	
 
    def get_headline(self):
 
        """
 
        Set headline based on project name.
 
        """
 

	
 
        return "Delete project %s" % self.object.name
 

	
 

	
 
class LocationCreateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, CreateView):
 
    """
 
    View for creating a new location.
 
    """
 

	
 
    model = Location
 
    form_class = LocationForm
 
    headline = "Add new location"
 
    template_name = "conntrackt/create_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.add_location",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    success_url = reverse_lazy("index")
 

	
 

	
 
class LocationUpdateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, UpdateView):
 
    """
 
    View for modifying an existing location.
 
    """
 

	
 
    model = Location
 
    form_class = LocationForm
 
    template_name = "conntrackt/update_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.change_location",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    success_url = reverse_lazy("index")
 

	
 
    def get_headline(self):
 
        """
 
        Set headline based on location name.
 
        """
 

	
 
        return "Update location %s" % self.object.name
 

	
 

	
 
class LocationDeleteView(RelatedItemsMixin, RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, DeleteView):
 
    """
 
    View for deleting a location.
 
    """
 

	
 
    model = Location
 
    template_name = "conntrackt/delete_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.delete_location",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    success_url = reverse_lazy("index")
 

	
 
    def post(self, *args, **kwargs):
 
        """
 
        Add a success message that will be displayed to the user to confirm the
 
        location deletion.
 
        """
 

	
 
        messages.success(self.request, "Location %s has been removed." % self.get_object().name, extra_tags="alert alert-success")
 

	
 
        return super(LocationDeleteView, self).post(*args, **kwargs)
 

	
 
    def get_headline(self):
 
        """
 
        Set headline based on location name.
 
        """
 

	
 
        return "Delete location %s" % self.object.name
 

	
 

	
 
class EntityCreateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, CreateView):
 
    """
 
    View for creating a new entity.
 
    """
 

	
 
    model = Entity
 
    form_class = EntityForm
 
    headline = "Add new entity"
 
    template_name = "conntrackt/create_form.html"
 

	
 
    # Required permissions.
 
    permissions = {
 
        "all": ("conntrackt.add_entity",),
 
        }
 

	
 
    # Raise authorisation denied exception for unmet permissions.
 
    raise_exception = True
 

	
 
    def get_form(self, form_class):
 
        """
 
        Returns an instance of form that can be used by the view.
 

	
 
        The method will limit the project or location select inputs if request
 
        contained this information.
 
        """
 

	
 
        form = super(EntityCreateView, self).get_form(form_class)
 

	
 
        # Limit the project selection if required.
 
        project_id = self.request.GET.get("project", None)
 
        if project_id:
 
            form.fields["project"].queryset = Project.objects.filter(pk=project_id)
 
            form.fields["project"].widget.attrs["readonly"] = True
 

	
 
        # Limit the location selection if required.
 
        location_id = self.request.GET.get("location", None)
 
        if location_id:
 
            form.fields["location"].queryset = Location.objects.filter(pk=location_id)
 
            form.fields["location"].widget.attrs["readonly"] = True
 

	
 
        return form
 

	
 
    def get_initial(self):
 
        """
 
        Returns initial values that should be pre-selected (if they were
 
        specified through a GET parameter).
 
        """
 

	
 
        initial = super(EntityCreateView, self).get_initial()
 

	
 
        initial["project"] = self.request.GET.get("project", None)
 
        initial["location"] = self.request.GET.get("location", None)
 

	
 
        return initial
 

	
 

	
 
class EntityUpdateView(RedirectToNextMixin, SetHeadlineMixin, MultiplePermissionsRequiredMixin, UpdateView):
 
    """
 
    View for updating an existing entity.
 
    """
 

	
 
    model = Entity
 
    form_class = EntityForm
 
    template_name = "conntrackt/update_form.html"
 

	
 
    # Required permissions.
0 comments (0 inline, 0 general)