Commit 60b6d979 by Czémán Arnold

network, settings: add basic network topology editor, eleminate pipeline cache, add ES6 compiler

parent da10884e
......@@ -22,6 +22,7 @@
"favico.js": "~0.3.5",
"datatables": "~1.10.4",
"chart.js": "2.3.0",
"clipboard": "~1.6.1"
"clipboard": "~1.6.1",
"jsPlumb": "2.5.7"
}
}
......@@ -165,16 +165,19 @@ p = normpath(join(SITE_ROOT, '../../site-circle/static'))
if exists(p):
STATICFILES_DIRS.append(p)
STATICFILES_STORAGE = 'pipeline.storage.PipelineCachedStorage'
STATICFILES_STORAGE = 'pipeline.storage.PipelineStorage'
PIPELINE = {
'COMPILERS' : ('pipeline.compilers.less.LessCompiler',),
'COMPILERS' : ('pipeline.compilers.less.LessCompiler',
'pipeline.compilers.es6.ES6Compiler', ),
'LESS_ARGUMENTS': u'--include-path={}'.format(':'.join(STATICFILES_DIRS)),
'CSS_COMPRESSOR': 'pipeline.compressors.yuglify.YuglifyCompressor',
'BABEL_ARGUMENTS': u'--presets env',
'JS_COMPRESSOR': None,
'DISABLE_WRAPPER': True,
'STYLESHEETS': {
"all": {"source_filenames": (
"all": {
"source_filenames": (
"compile_bootstrap.less",
"bootstrap/dist/css/bootstrap-theme.css",
"fontawesome/css/font-awesome.css",
......@@ -187,10 +190,17 @@ PIPELINE = {
"autocomplete_light/select2.css",
),
"output_filename": "all.css",
}
},
"network-editor": {
"source_filenames": (
"network/editor.css",
),
"output_filename": "network-editor.css",
},
},
'JAVASCRIPT': {
"all": {"source_filenames": (
"all": {
"source_filenames": (
# "jquery/dist/jquery.js", # included separately
"bootbox/bootbox.js",
"bootstrap/dist/js/bootstrap.js",
......@@ -203,6 +213,7 @@ PIPELINE = {
"autocomplete_light/autocomplete.init.js",
"autocomplete_light/vendor/select2/dist/js/select2.js",
"autocomplete_light/select2.js",
"jsPlumb/dist/js/dom.jsPlumb-1.7.5-min.js",
"dashboard/dashboard.js",
"dashboard/activity.js",
"dashboard/group-details.js",
......@@ -225,7 +236,8 @@ PIPELINE = {
),
"output_filename": "all.js",
},
"vm-detail": {"source_filenames": (
"vm-detail": {
"source_filenames": (
"clipboard/dist/clipboard.min.js",
"dashboard/vm-details.js",
"no-vnc/include/util.js",
......@@ -245,12 +257,20 @@ PIPELINE = {
),
"output_filename": "vm-detail.js",
},
"datastore": {"source_filenames": (
"datastore": {
"source_filenames": (
"chart.js/dist/Chart.min.js",
"dashboard/datastore-details.js"
),
"output_filename": "datastore.js",
},
"network-editor": {
"source_filenames": (
"jsPlumb/dist/js/jsplumb.min.js",
"network/editor.es6",
),
"output_filename": "network-editor.js",
},
},
}
......
......@@ -112,6 +112,7 @@ if DEBUG:
PIPELINE["COMPILERS"] = (
'dashboard.compilers.DummyLessCompiler',
'pipeline.compilers.es6.ES6Compiler',
)
ADMIN_ENABLED = True
......
{% extends "dashboard/base.html" %}
{% load staticfiles %}
{% load i18n %}
{% load pipeline %}
{% block title-page %}{% trans 'Network Editor' %}{% endblock %}
{% block extra_css %}
{% stylesheet "network-editor" %}
{% endblock %}
{% block content %}
<div class="flex-container" id="workspace">
<div class="panel panel-default text-center" id="dragPanel">
<div class="panel-heading">
<div class="row">
<div class="col-md-9 text-left">
<h3 class="no-margin"><i class="fa fa-sitemap"></i> {% trans 'Editor' %}</h3>
</div>
<div class="col-md-3 text-left">
<button class="btn btn-success btn-xs" id="saveButton"><i class="fa fa-floppy-o"></i></button>
</div>
</div>
</div>
<div class="panel-heading text-center">
<div id="filterConatiner">
<div class="row">
<input type="text" class="form-control" id="searchField" placeholder="{% trans 'Search' %}"/><br />
</div>
<div class="row">
<div class="col-md-6">
<i id="vm-filter"></i> <i class="fa fa-desktop"></i> vm
</div>
<div class="col-md-6">
<i id="net-filter"></i> <i class="fa fa-sitemap"></i> net
</div>
</div>
</div>
</div>
<div class="panel-body" id="dragContainer">
</div>
</div>
<div class="" id="dropContainer" oncontextmenu="return false;"></div>
</div>
{% endblock %}
{% block extra_js %}
{% javascript "network-editor" %}
{% endblock %}
......@@ -31,7 +31,7 @@ from .views import (
FirewallList, FirewallDetail, FirewallCreate, FirewallDelete,
remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device,
VlanAclUpdateView
VlanAclUpdateView, NetworkEditorView
)
urlpatterns = [
......@@ -135,6 +135,10 @@ urlpatterns = [
url('^vxlans/delete/(?P<vni>\d+)/$', VxlanDelete.as_view(),
name="network.vxlan-delete"),
# editor
url('^editor/$', NetworkEditorView.as_view(),
name="network.editor"),
# non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
name='network.remove_host_group'),
......
......@@ -17,11 +17,12 @@
import logging
import random
import json
from collections import OrderedDict
from netaddr import IPNetwork
from django.views.generic import (
TemplateView, UpdateView, DeleteView, CreateView
TemplateView, UpdateView, DeleteView, CreateView,
)
from django.core.exceptions import (
ValidationError, PermissionDenied, ImproperlyConfigured
......@@ -38,8 +39,8 @@ from firewall.models import (
Host, Vlan, Domain, Group, Record, BlacklistItem, Rule, VlanGroup,
SwitchPort, EthernetDevice, Firewall
)
from network.models import Vxlan
from vm.models import Interface
from network.models import Vxlan, EditorElement
from vm.models import Interface, Instance
from common.views import CreateLimitedResourceMixin
from acl.views import CheckedObjectMixin
from .tables import (
......@@ -1107,6 +1108,192 @@ class VxlanDelete(LoginRequiredMixin, CheckedObjectMixin, DeleteView):
return context
class NetworkEditorView(LoginRequiredMixin, TemplateView):
template_name = 'network/editor.html'
def get(self, *args, **kwargs):
if self.request.is_ajax():
connections = self._get_connections()
ngelements = self._get_nongraph_elements(connections)
ngelements = self._serialize_elements(ngelements)
connections = map(lambda con: {
'source': 'vm-%s' % con['source'].pk,
'target': 'net-%s' % con['target'].vni,
}, connections['connections'])
unused_elements = self._get_unused_elements()
unused_elements = self._serialize_elements(unused_elements)
return JsonResponse({
'elements': map(lambda e: e.as_data(),
EditorElement.objects.filter(
owner=self.request.user)),
'nongraph_elements': ngelements,
'unused_elements': unused_elements,
'connections': connections,
})
return super(NetworkEditorView, self).get(*args, **kwargs)
def post(self, *args, **kwargs):
data = json.loads(self.request.body)
add_ifs = data.get('add_interfaces', [])
remove_ifs = data.get('remove_interfaces', [])
add_nodes = data.get('add_nodes', [])
remove_nodes = data.get('remove_nodes', [])
# Add editor element
self._element_list_operation(add_nodes, self._update_element)
# Remove editor element
self._element_list_operation(remove_nodes, self._remove_element)
# Add interface
self._interface_list_operation(add_ifs, self._add_interface)
# Remove interface
self._interface_list_operation(remove_ifs, self._remove_interface)
return self.get(*args, **kwargs)
def _max_port_num_helper(self, model, attr_name):
if not hasattr(self, attr_name):
value = model.get_objects_with_level(
'user', self.request.user).count()
setattr(self, attr_name, value)
return getattr(self, attr_name)
@property
def vm_max_port_num(self):
return self._max_port_num_helper(Vxlan, '_vm_max_port_num')
@property
def vxlan_max_port_num(self):
return self._max_port_num_helper(Instance, '_vxlan_max_port_num')
def _vm_serializer(self, vm):
max_port_num = self.vm_max_port_num
vxlans = Vxlan.get_objects_with_level(
'user', self.request.user).values_list('pk', flat=True)
free_port_num = max_port_num - vm.interface_set.filter(
vxlan__pk__in=vxlans).count()
return {
'name': unicode(vm),
'id': 'vm-%s' % vm.pk,
'description': vm.description,
'type': 'vm',
'icon': 'fa-desktop',
'free_port_num': free_port_num,
}
def _vxlan_serializer(self, vxlan):
max_port_num = self.vxlan_max_port_num
vms = Instance.get_objects_with_level(
'user', self.request.user).values_list('pk', flat=True)
free_port_num = max_port_num - Interface.objects.filter(
vxlan=vxlan, instance__pk__in=vms).count()
return {
'name': vxlan.name,
'id': 'net-%s' % vxlan.vni,
'description': vxlan.description,
'type': 'network',
'icon': 'fa-sitemap',
'free_port_num': free_port_num,
}
def _get_unused_elements(self):
connections = self._get_connections()
vms = map(lambda vm: vm.id, connections['vms'])
vxlans = map(lambda vxlan: vxlan.vni, connections['vxlans'])
eelems = EditorElement.objects.filter(owner=self.request.user)
vm_query = Q(pk__in=vms) | Q(editor_elements__in=eelems)
vms = Instance.get_objects_with_level(
'user', self.request.user).exclude(vm_query)
vxlan_query = Q(vni__in=vms) | Q(editor_elements__in=eelems)
vxlans = Vxlan.get_objects_with_level(
'user', self.request.user).exclude(vxlan_query)
return {
'vms': vms,
'vxlans': vxlans,
}
def _get_nongraph_elements(self, connections):
return {
'vms': filter(lambda v: not v.editor_elements.exists(),
connections['vms']),
'vxlans': filter(lambda v: not v.editor_elements.exists(),
connections['vxlans']),
}
def _get_connections(self):
""" Returns connections and theirs participants. """
vms = Instance.get_objects_with_level('user', self.request.user)
connections = []
vm_set = set()
vxlan_set = set()
for vm in vms:
for intf in vm.interface_set.filter(vxlan__isnull=False):
vm_set.add(vm)
vxlan_set.add(intf.vxlan)
connections.append({
'source': vm,
'target': intf.vxlan,
})
return {
'connections': connections,
'vms': vm_set,
'vxlans': vxlan_set,
}
def _serialize_elements(self, elements):
return (map(self._vm_serializer, elements['vms']) +
map(self._vxlan_serializer, elements['vxlans']))
def _get_modifiable_object(self, model, connection,
attr_name, filter_attr):
value = connection.get(attr_name)
if value is not None:
value = model.get_objects_with_level(
'user', self.request.user).filter(
**{filter_attr: value}).first()
return value
def _element_list_operation(self, node_list, operation):
for e in node_list:
elem = dict(e)
type = elem.pop('type')
id = elem.pop('id')
model = Instance if type == 'vm' else Vxlan
filter = {'pk': id} if type == 'vm' else {'vni': id}
object = model.get_objects_with_level(
'user', self.request.user).get(**filter)
operation(object.editor_elements, elem)
def _update_element(self, elements, elem):
elements.update_or_create(owner=self.request.user,
defaults=elem)
def _remove_element(self, elements, elem):
elements.filter(owner=self.request.user).delete()
def _interface_list_operation(self, if_list, operation):
for con in if_list:
vm = self._get_modifiable_object(Instance, con, 'source', 'pk')
vxlan = self._get_modifiable_object(Vxlan, con, 'target', 'vni')
if vm and vxlan:
operation(vm, vxlan)
def _add_interface(self, vm, vxlan):
vm.add_user_interface(
user=self.request.user, vxlan=vxlan, system=vm.system)
def _remove_interface(self, vm, vxlan):
intf = vm.interface_set.filter(vxlan=vxlan).first()
if intf:
vm.remove_user_interface(
interface=intf, user=self.request.user, system=vm.system)
def remove_host_group(request, **kwargs):
host = Host.objects.get(pk=kwargs['pk'])
group = Group.objects.get(pk=kwargs['group_pk'])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment