Commit d5c96378 by Szabolcs Gelencser

Add initial network topology view (editor)

parent 09b07ed6
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
"datatables": "~1.10.4", "datatables": "~1.10.4",
"chart.js": "2.3.0", "chart.js": "2.3.0",
"clipboard": "~1.6.1", "clipboard": "~1.6.1",
"jsPlumb": "2.5.7" "jsPlumb": "2.5.7",
"d3": "3.5.16",
"hogan.js": "hogan#^3.0.2",
"angular": "1.5.8"
} }
} }
...@@ -253,6 +253,19 @@ PIPELINE = { ...@@ -253,6 +253,19 @@ PIPELINE = {
), ),
"output_filename": "datastore.js", "output_filename": "datastore.js",
}, },
"editor": {"source_filenames": (
"d3/d3.js",
"hogan.js/web/builds/2.0.0/hogan-2.0.0.js",
"angular/angular.min.js",
"js/horizon.js",
"js/horizon.loader.js",
"js/horizon.templates.js",
"js/horizon.flatnetworktopology.js",
"js/horizon.networktopology.js",
"js/horizon.networktopologycommon.js",
),
"output_filename": "editor.js",
},
}, },
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
{% block navbar %} {% block navbar %}
{% if view.request.user.is_authenticated and view.request.user.pk and not view.request.token_user %} {% if view.request.user.is_authenticated and view.request.user.pk and not view.request.token_user %}
<span id="user-options" data-desktop_notifications="{{ request.user.profile.desktop_notifications }}"><span> <span id="user-options" data-desktop_notifications="{{ request.user.profile.desktop_notifications }}"></span>
<ul class="nav navbar-nav navbar-right" id="dashboard-menu"> <ul class="nav navbar-nav navbar-right" id="dashboard-menu">
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
......
...@@ -88,9 +88,9 @@ horizon.flat_network_topology = { ...@@ -88,9 +88,9 @@ horizon.flat_network_topology = {
return; return;
} }
self.color = d3.scale.category10(); self.color = d3.scale.category10();
self.balloon_tmpl = Hogan.compile($('#balloon_container').html()); self.balloon_tmpl = '<span>balloon_tmpl</span>'
self.balloon_device_tmpl = Hogan.compile($('#balloon_device').html()); self.balloon_device_tmpl = '<span>balloon_device_tmpl</span>'
self.balloon_port_tmpl = Hogan.compile($('#balloon_port').html()); self.balloon_port_tmpl = '<span>balloon_port_tmpl</span>'
$(document) $(document)
.on('click', 'a.closeTopologyBalloon', function(e) { .on('click', 'a.closeTopologyBalloon', function(e) {
......
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/* This is the base Horizon JavaScript object. There is only ever one of these
* loaded (referenced as horizon with a lower-case h) which happens immediately
* after the definition below.
*
* Scripts that are dependent on functionality defined in the Horizon object
* must be included after this script in templates/base.html.
*/
var Horizon = function () {
var horizon = {},
initFunctions = [];
/* Use the addInitFunction() function to add initialization code which must
* be called on DOM ready. This is useful for adding things like event
* handlers or any other initialization functions which should precede user
* interaction but rely on DOM readiness.
*/
horizon.addInitFunction = function (fn) {
initFunctions.push(fn);
};
/* Call all initialization functions and clear the queue. */
horizon.init = function () {
for (var i = 0; i < initFunctions.length; i += 1) {
initFunctions[i]();
}
// Prevent multiple executions, just in case.
initFunctions = [];
};
/* Storage for backend configuration variables which the frontend
* should be aware of.
*/
horizon.conf = {};
// default languageCode for tests that run without Django context
horizon.languageCode = 'en';
return horizon;
};
// Create the one and only horizon object.
/*eslint-disable no-unused-vars */
var horizon = new Horizon();
/*eslint-enable no-unused-vars */
/*
Simple loader rendering logic
*/
horizon.loader = {
templates: {
inline: '#loader-inline',
modal: '#loader-modal'
}
};
horizon.loader.inline = function(text) {
return horizon.templates.compile(horizon.loader.templates.inline, {text: text});
};
horizon.loader.modal = function(text) {
return horizon.templates.compile(horizon.loader.templates.modal, {text: text});
};
...@@ -111,11 +111,11 @@ horizon.network_topology = { ...@@ -111,11 +111,11 @@ horizon.network_topology = {
self.data.ports = {}; self.data.ports = {};
// Setup balloon popups // Setup balloon popups
self.balloonTmpl = Hogan.compile(angular.element('#balloon_container').html()); self.balloonTmpl = "<span>balloonTmpl</span>"
self.balloon_deviceTmpl = Hogan.compile(angular.element('#balloon_device').html()); self.balloon_deviceTmpl = "<span>balloon_deviceTmpl</span>"
self.balloon_portTmpl = Hogan.compile(angular.element('#balloon_port').html()); self.balloon_portTmpl = "<span>balloon_portTmpl</span>"
self.balloon_netTmpl = Hogan.compile(angular.element('#balloon_net').html()); self.balloon_netTmpl = "<span>balloon_netTmpl</span>"
self.balloon_instanceTmpl = Hogan.compile(angular.element('#balloon_instance').html()); self.balloon_instanceTmpl = "<span>balloon_instanceTmpl</span>"
angular.element(document) angular.element(document)
.on('click', 'a.closeTopologyBalloon', function(e) { .on('click', 'a.closeTopologyBalloon', function(e) {
......
/**
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/* global Hogan */
/* Namespace for core functionality related to client-side templating. */
horizon.templates = {
template_ids: [
"#modal_template",
"#empty_row_template",
"#alert_message_template",
"#loader-modal",
"#loader-inline",
"#membership_template",
"#confirm_modal",
"#progress-modal"
],
compiled_templates: {}
};
/* Pre-loads and compiles the client-side templates. */
horizon.templates.compile_templates = function (id) {
// If an id is passed in, only compile that template
if (id) {
horizon.templates.compiled_templates[id] = Hogan.compile($(id).html());
} else {
// If its never been set, make it an empty object
horizon.templates.compiled_templates =
$.isEmptyObject(horizon.templates.compiled_templates) ? {} : horizon.templates.compiled_templates;
// Over each template found, only recompile ones that need it
$.each(horizon.templates.template_ids, function (ind, template_id) {
if (!(template_id in horizon.templates.compiled_templates)) {
horizon.templates.compiled_templates[template_id] = Hogan.compile($(template_id).html());
}
});
}
};
/* Takes a template id, as defined in horizon.templates.template_ids, and returns the compiled
template given the context passed in, as a jQuery object
*/
horizon.templates.compile = function(id, context) {
var template = horizon.templates.compiled_templates[id];
// If its not available, maybe we didn't compile it yet, try one more time
if (!template) {
horizon.templates.compile_templates(id);
template = horizon.templates.compiled_templates[id];
}
return $(template.render(context));
};
horizon.addInitFunction(horizon.templates.init = function () {
// Load client-side template fragments and compile them.
horizon.templates.compile_templates();
});
...@@ -11,43 +11,70 @@ ...@@ -11,43 +11,70 @@
{% block content %} {% block content %}
<div class="flex-container" id="workspace"> <div id="workspace">
<div class="row">
<div class="panel panel-default text-center" id="dragPanel"> <div class="col-sm-12">
<div class="panel-heading"> <div class="topology-navi">
<div class="row"> <div class="btn-group" data-toggle="buttons">
<div class="col-md-9 text-left"> <label class="btn btn-default" id="toggle_labels_label">
<h3 class="no-margin"><i class="fa fa-sitemap"></i> {% trans 'Editor' %}</h3> <input type="checkbox" autocomplete="off" id="toggle_labels">
</div> <span class="fa fa-th-large"></span> {% trans "Toggle Labels" %}
<div class="col-md-3 text-right"> </label>
<button class="btn btn-success btn-xs" id="saveButton"><i class="fa fa-floppy-o"></i></button> <label class="btn btn-default" id="toggle_networks_label">
</div> <input type="checkbox" autocomplete="off" id="toggle_networks">
</div> <span class="fa fa-th"></span> {% trans "Toggle Network Collapse" %}
</div> </label>
<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>
<div class="col-md-6"> </div>
<i id="net-filter"></i> <i class="fa fa-sitemap"></i> net <div id="topologyCanvasContainer" class="d3-container">
<div class="nodata">
{% blocktrans trimmed %}
There are no networks, routers, or connected instances to display.
{% endblocktrans %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="panel-body" id="dragContainer">
</div> <span data-networktopology="/network/editor/data" id="networktopology"></span>
<div id="topologyMessages"></div>
</div> </div>
<div class="" id="dropContainer" oncontextmenu="return false;"></div> {% include "network/horizon/client_side/templates.html" %}
</div>
{% endblock %} {% endblock %}
{% block extra_js %} {% block extra_js %}
{% javascript "network-editor" %} {% javascript "editor" %}
<script type="text/javascript">
{#TODO: real cookie storage#}
var cookies = {}
horizon.cookies = {
getObject: function (x) {
return cookies[x]
}, put: function (x, y) {
cookies[x] = y
}, putObject: function (x, y) {
cookies[x] = y
}, getRaw: function (x) {
return cookies[x]
}, get: function (x) {
return cookies[x]
}
};
if (typeof horizon.network_topology !== 'undefined') {
{#TODO: this check should be false first#}
horizon.networktopologycommon.init();
horizon.flat_network_topology.init();
horizon.network_topology.init();
} else {
addHorizonLoadEvent(function () {
horizon.networktopologycommon.init();
horizon.flat_network_topology.init();
horizon.network_topology.init();
});
}
</script>
{% endblock %} {% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}alert_message_template{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="alert alert-dismissable fade in alert-[[type]]">
<a class="close" data-dismiss="alert" href="#">
<span class="fa fa-times"></span>
</a>
<p>
<strong>[[type_display]]</strong>
[[#safe]]
[[[message]]]
[[/safe]]
[[^safe]]
[[message]]
[[/safe]]
</p>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load i18n horizon %}
{% block id %}confirm_modal{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="confirm-wrapper">
<span class="confirm-list">
{% blocktrans %}You have selected: [[selection]]. {% endblocktrans %}
</span>
<span class="confirm-text">{% trans 'Please confirm your selection.'%} </span>
<span class="confirm-help">[[help]]</span>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load i18n horizon %}
{% block id %}loader-inline{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="loader-inline">
<span class="loader fa fa-spinner fa-spin fa-4x text-center"></span>
<div class="loader-caption h4 text-center">[[text]]&hellip;</div>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load i18n horizon %}
{% block id %}loader-modal{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="modal loading">
<div class="modal-dialog modal-xs">
<div class="modal-content">
<div class="modal-body">
<span class="loader fa fa-spinner fa-spin fa-5x text-center"></span>
<div class="loader-caption h4 text-center">[[text]]&hellip;</div>
</div>
</div>
</div>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}membership_template{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<ul class="nav nav-pills btn-group btn-group-sm">
<li class="member" data-[[step_slug]]-id="[[data_id]]">
<span class="display_name">[[display_name]]</span>
</li>
<li class="active"><a class="btn btn-primary" href="#add_remove">[[text]]</a></li>
<li class="dropdown role_options">
<a class="dropdown-toggle btn btn-default" data-toggle="dropdown" href="#">
<span class="roles_display">[[roles_label]]</span>
<span class="fa fa-caret-down"></span>
</a>
<ul class="dropdown-menu dropdown-menu-right role_dropdown">
[[#roles]]
<li data-role-id="[[role_id]]">
<a target="_blank">
<span class="fa fa-check"></span>
[[role_name]]
</a>
</li>
[[/roles]]
</ul>
</li>
</ul>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}modal_template{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="modal" data-backdrop="[[modal_backdrop]]">
<div class="modal-dialog">
<div class="modal-content">
<div class='modal-header'>
<a class='close' data-dismiss='modal' href="#">
<span class="fa fa-times"></span>
</a>
<h3 class="modal-title">[[title]]</h3>
</div>
<div class='modal-body'>
[[[body]]]
</div>
<div class='modal-footer'>
<a href='#' class='btn btn-default cancel' data-dismiss='modal'>[[cancel]]</a>
<a href='#' class='btn [[confirmCssClass]]'>[[confirm]]</a>
</div>
</div>
</div>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
{% extends "network/horizon/client_side/template.html" %}
{% load i18n horizon bootstrap %}
{% block id %}progress-modal{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<div class="modal loading">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-body">
<div class="modal-progress-loader">
{% bs_progress_bar 0 text="0%" %}
<div class="progress-label text-center h4">[[text]]</div>
</div>
</div>
</div>
</div>
</div>
{% endjstemplate %}{% endspaceless %}{% endblock %}
<script type="text/javascript" charset="utf-8">
/*
Added so that we can append Horizon scoped JS events to
the DOM load events without running in to the "horizon"
name-space not currently being defined since we load the
scripts at the bottom of the page.
*/
var addHorizonLoadEvent = function(func) {
var old_onload = window.onload;
if (typeof window.onload != 'function') {
window.onload = func;
} else {
window.onload = function() {
old_onload();
func();
}
}
}
</script>
{% extends "network/horizon/client_side/template.html" %}
{% load horizon %}
{% block id %}empty_row_template{% endblock %}
{% block template %}{% spaceless %}{% jstemplate %}
<tr class="odd empty"><td colspan="[[colspan]]">[[no_items_label]]</td></tr>
{% endjstemplate %}{% endspaceless %}{% endblock %}
<script type="text/html" id="{% block id %}{% endblock %}">{% block template %}{% endblock %}</script>
{#{% include "network/horizon/client_side/_modal.html" %}#}
{#{% include "network/horizon/client_side/_table_row.html" %}#}
{#{% include "network/horizon/client_side/_alert_message.html" %}#}
{% include "network/horizon/client_side/_loading_modal.html" %}
{% include "network/horizon/client_side/_loading_inline.html" %}
{#{% include "network/horizon/client_side/_membership.html" %}#}
{#{% include "network/horizon/client_side/_confirm.html" %}#}
{#{% include "network/horizon/client_side/_progress.html" %}#}
\ No newline at end of file
# Copyright 2012 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from django import template
register = template.Library()
class JSTemplateNode(template.Node):
"""Helper node for the ``jstemplate`` template tag."""
def __init__(self, nodelist):
self.nodelist = nodelist
def render(self, context,):
output = self.nodelist.render(context)
output = output.replace('[[[', '{{{').replace(']]]', '}}}')
output = output.replace('[[', '{{').replace(']]', '}}')
output = output.replace('[%', '{%').replace('%]', '%}')
return output
@register.tag
def jstemplate(parser, token):
"""Templatetag to handle any of the Mustache-based templates.
Replaces ``[[[`` and ``]]]`` with ``{{{`` and ``}}}``,
``[[`` and ``]]`` with ``{{`` and ``}}`` and
``[%`` and ``%]`` with ``{%`` and ``%}`` to avoid conflicts
with Django's template engine when using any of the Mustache-based
templating libraries.
"""
nodelist = parser.parse(('endjstemplate',))
parser.delete_first_token()
return JSTemplateNode(nodelist)
...@@ -31,8 +31,8 @@ from .views import ( ...@@ -31,8 +31,8 @@ from .views import (
FirewallList, FirewallDetail, FirewallCreate, FirewallDelete, FirewallList, FirewallDetail, FirewallCreate, FirewallDelete,
remove_host_group, add_host_group, remove_host_group, add_host_group,
remove_switch_port_device, add_switch_port_device, remove_switch_port_device, add_switch_port_device,
VlanAclUpdateView, NetworkEditorView VlanAclUpdateView, NetworkEditorView,
) NetworkEditorDataView)
urlpatterns = [ urlpatterns = [
url('^$', IndexView.as_view(), name='network.index'), url('^$', IndexView.as_view(), name='network.index'),
...@@ -137,8 +137,8 @@ urlpatterns = [ ...@@ -137,8 +137,8 @@ urlpatterns = [
name="network.vxlan-delete"), name="network.vxlan-delete"),
# editor # editor
url('^editor/$', NetworkEditorView.as_view(), url('^editor/$', NetworkEditorView.as_view(), name="network.editor"),
name="network.editor"), url('^editor/data', NetworkEditorDataView.as_view(), name="network.editor.data"),
# non class based views # non class based views
url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group, url('^hosts/(?P<pk>\d+)/remove/(?P<group_pk>\d+)/$', remove_host_group,
......
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