first commit 63/6063/1
authorlombardof <flombardo@cnit.it>
Thu, 3 May 2018 14:20:04 +0000 (16:20 +0200)
committerlombardof <flombardo@cnit.it>
Thu, 3 May 2018 14:20:04 +0000 (16:20 +0200)
Change-Id: I8a65ee5527dd16d81e87c8ac5d4bdb471e5e759d
Signed-off-by: lombardof <flombardo@cnit.it>
165 files changed:
.bowerrc [new file with mode: 0644]
.gitignore [new file with mode: 0644]
Dockerfile [new file with mode: 0644]
README.md [new file with mode: 0644]
bower.json [new file with mode: 0644]
instancehandler/__init__.py [new file with mode: 0644]
instancehandler/admin.py [new file with mode: 0644]
instancehandler/apps.py [new file with mode: 0644]
instancehandler/models.py [new file with mode: 0644]
instancehandler/template/instance_list.html [new file with mode: 0644]
instancehandler/template/modal/instance_create.html [new file with mode: 0644]
instancehandler/template/modal/instance_new_action.html [new file with mode: 0644]
instancehandler/template/modal/instance_show.html [new file with mode: 0644]
instancehandler/tests.py [new file with mode: 0644]
instancehandler/urls.py [new file with mode: 0644]
instancehandler/views.py [new file with mode: 0644]
lib/TopologyModels/example/example.yaml [new file with mode: 0644]
lib/TopologyModels/osm/osm.yaml [new file with mode: 0644]
lib/__init__.py [new file with mode: 0644]
lib/osm/__init__.py [new file with mode: 0644]
lib/osm/osm_parser.py [new file with mode: 0644]
lib/osm/osm_rdcl_graph.py [new file with mode: 0644]
lib/osm/osmclient/__init__.py [new file with mode: 0644]
lib/osm/osmclient/client.py [new file with mode: 0644]
lib/parser.py [new file with mode: 0644]
lib/rdcl_graph.py [new file with mode: 0644]
lib/util.py [new file with mode: 0644]
manage.py [new file with mode: 0755]
projecthandler/__init__.py [new file with mode: 0644]
projecthandler/admin.py [new file with mode: 0644]
projecthandler/apps.py [new file with mode: 0644]
projecthandler/management/__init__.py [new file with mode: 0644]
projecthandler/management/commands/__init__.py [new file with mode: 0644]
projecthandler/management/commands/delete_project_type.py [new file with mode: 0644]
projecthandler/management/commands/new_project_type.py [new file with mode: 0644]
projecthandler/models.py [new file with mode: 0644]
projecthandler/osm_model.py [new file with mode: 0644]
projecthandler/template/project/descriptor/descriptor_view_base.html [new file with mode: 0644]
projecthandler/template/project/descriptor/modal/choose_node_id.html [new file with mode: 0644]
projecthandler/template/project/modal/modal_keyboard_info_base.html [new file with mode: 0644]
projecthandler/template/project/new_project.html [new file with mode: 0644]
projecthandler/template/project/osm/descriptor/descriptor_new.html [new file with mode: 0644]
projecthandler/template/project/osm/descriptor/descriptor_view.html [new file with mode: 0644]
projecthandler/template/project/osm/descriptor/descriptorlist.html [new file with mode: 0644]
projecthandler/template/project/osm/descriptor/modal/choose_node_id.html [new file with mode: 0644]
projecthandler/template/project/osm/descriptor/modal/create_link_chooser.html [new file with mode: 0644]
projecthandler/template/project/osm/modal/files_list.html [new file with mode: 0644]
projecthandler/template/project/osm/modal/modal_keyboard_info.html [new file with mode: 0644]
projecthandler/template/project/osm/osm_new_project.html [new file with mode: 0644]
projecthandler/template/project/osm/osm_project_delete.html [new file with mode: 0644]
projecthandler/template/project/osm/osm_project_descriptors.html [new file with mode: 0644]
projecthandler/template/project/osm/osm_project_details.html [new file with mode: 0644]
projecthandler/template/project/osm/osm_project_left_sidebar.html [new file with mode: 0644]
projecthandler/template/project/osm/project_graph.html [new file with mode: 0644]
projecthandler/template/project/osm/topology_toolbar.html [new file with mode: 0644]
projecthandler/template/project/project_delete.html [new file with mode: 0644]
projecthandler/template/project/project_descriptors.html [new file with mode: 0644]
projecthandler/template/project/project_details.html [new file with mode: 0644]
projecthandler/template/project/project_graph_base.html [new file with mode: 0644]
projecthandler/template/project/projectlist.html [new file with mode: 0644]
projecthandler/template/project/repository/repo_base_left_sidebar.html [new file with mode: 0644]
projecthandler/template/project/repository/repo_create_modal.html [new file with mode: 0644]
projecthandler/template/project/repository/repo_list.html [new file with mode: 0644]
projecthandler/tests.py [new file with mode: 0644]
projecthandler/urls/__init__.py [new file with mode: 0644]
projecthandler/urls/project.py [new file with mode: 0644]
projecthandler/views.py [new file with mode: 0644]
requirements.txt [new file with mode: 0644]
sf_t3d/__init__.py [new file with mode: 0644]
sf_t3d/context_processor.py [new file with mode: 0644]
sf_t3d/settings.py [new file with mode: 0644]
sf_t3d/templatetags/__init__.py [new file with mode: 0644]
sf_t3d/templatetags/get.py [new file with mode: 0644]
sf_t3d/urls.py [new file with mode: 0644]
sf_t3d/views.py [new file with mode: 0644]
sf_t3d/wsgi.py [new file with mode: 0644]
sf_user/__init__.py [new file with mode: 0644]
sf_user/admin.py [new file with mode: 0644]
sf_user/apps.py [new file with mode: 0644]
sf_user/management/__init__.py [new file with mode: 0644]
sf_user/management/commands/__init__.py [new file with mode: 0644]
sf_user/management/commands/clean_guest_data.py [new file with mode: 0644]
sf_user/management/commands/clearsessions.py [new file with mode: 0644]
sf_user/models.py [new file with mode: 0644]
sf_user/sessions.py [new file with mode: 0644]
sf_user/tests.py [new file with mode: 0644]
sf_user/views.py [new file with mode: 0644]
static/assets/img/OSM-logo.png [new file with mode: 0644]
static/assets/img/account_circle.png [new file with mode: 0644]
static/assets/img/ce.png [new file with mode: 0755]
static/assets/img/cloudnode.png [new file with mode: 0755]
static/assets/img/controller-256.png [new file with mode: 0755]
static/assets/img/cp-80.png [new file with mode: 0755]
static/assets/img/docker.png [new file with mode: 0644]
static/assets/img/employer.jpg [new file with mode: 0755]
static/assets/img/employer.png [new file with mode: 0644]
static/assets/img/euh.png [new file with mode: 0755]
static/assets/img/favicon.ico [new file with mode: 0644]
static/assets/img/host-256.png [new file with mode: 0755]
static/assets/img/host.png [new file with mode: 0755]
static/assets/img/k8s.png [new file with mode: 0644]
static/assets/img/l2sw.png [new file with mode: 0755]
static/assets/img/ofcontroller.png [new file with mode: 0755]
static/assets/img/ofl2sw.png [new file with mode: 0755]
static/assets/img/osm_logo.png [new file with mode: 0644]
static/assets/img/osm_logo.svg [new file with mode: 0644]
static/assets/img/osm_small_logo.png [new file with mode: 0644]
static/assets/img/punto.png [new file with mode: 0755]
static/assets/img/rdcl3dlogo0.png [new file with mode: 0644]
static/assets/img/rdcl3dlogo0.svg [new file with mode: 0644]
static/assets/img/router.png [new file with mode: 0755]
static/assets/img/server.png [new file with mode: 0644]
static/assets/img/sf_logo.png [new file with mode: 0644]
static/assets/img/sf_logo_big.png [new file with mode: 0644]
static/assets/img/sf_small_logo.png [new file with mode: 0644]
static/assets/img/switch.png [new file with mode: 0755]
static/assets/img/vl-80.png [new file with mode: 0755]
static/assets/img/vnf-100.png [new file with mode: 0755]
static/assets/img/vs.png [new file with mode: 0755]
static/css/rdcl.css [new file with mode: 0644]
static/src/adminlte_session_storage.js [new file with mode: 0644]
static/src/instancehandler/instance_list.js [new file with mode: 0644]
static/src/projecthandler/descriptorslist.js [new file with mode: 0644]
static/src/projecthandler/new_project.js [new file with mode: 0644]
static/src/projecthandler/osm/controller.js [new file with mode: 0644]
static/src/projecthandler/osm/gui_properties.js [new file with mode: 0644]
static/src/projecthandler/osm/project_graph.js [new file with mode: 0644]
static/src/utils.js [new file with mode: 0644]
static/topology3D/css/d3-context-menu.css [new file with mode: 0644]
static/topology3D/css/graph_editor_d3js.css [new file with mode: 0755]
static/topology3D/js/d3-context-menu.js [new file with mode: 0644]
static/topology3D/js/event.js [new file with mode: 0755]
static/topology3D/js/graph_editor.js [new file with mode: 0755]
static/topology3D/js/graph_request.js [new file with mode: 0644]
static/topology3D/js/model_graph_editor.js [new file with mode: 0644]
template/base.html [new file with mode: 0644]
template/error.html [new file with mode: 0644]
template/footer.html [new file with mode: 0644]
template/forbidden.html [new file with mode: 0644]
template/head.html [new file with mode: 0644]
template/home.html [new file with mode: 0644]
template/left_sidebar_base.html [new file with mode: 0644]
template/login.html [new file with mode: 0644]
template/logo_sidebar.html [new file with mode: 0644]
template/modals/error_alert.html [new file with mode: 0644]
template/topology_toolbar.html [new file with mode: 0644]
vimhandler/__init__.py [new file with mode: 0644]
vimhandler/admin.py [new file with mode: 0644]
vimhandler/apps.py [new file with mode: 0644]
vimhandler/models.py [new file with mode: 0644]
vimhandler/template/config/aws.html [new file with mode: 0644]
vimhandler/template/config/aws_show.html [new file with mode: 0644]
vimhandler/template/config/openstack.html [new file with mode: 0644]
vimhandler/template/config/openstack_show.html [new file with mode: 0644]
vimhandler/template/config/openvim.html [new file with mode: 0644]
vimhandler/template/config/openvim_show.html [new file with mode: 0644]
vimhandler/template/config/vmware.html [new file with mode: 0644]
vimhandler/template/config/vmware_show.html [new file with mode: 0644]
vimhandler/template/modal/vim_details.html [new file with mode: 0644]
vimhandler/template/vim_create.html [new file with mode: 0644]
vimhandler/template/vim_list.html [new file with mode: 0644]
vimhandler/template/vim_show.html [new file with mode: 0644]
vimhandler/tests.py [new file with mode: 0644]
vimhandler/urls.py [new file with mode: 0644]
vimhandler/views.py [new file with mode: 0644]

diff --git a/.bowerrc b/.bowerrc
new file mode 100644 (file)
index 0000000..c7fde75
--- /dev/null
+++ b/.bowerrc
@@ -0,0 +1,3 @@
+{
+"directory": "static/bower_components"
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..06fb943
--- /dev/null
@@ -0,0 +1,47 @@
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+.Python
+env/
+.idea
+db.sqlite3
+.idea
+wheelhouse/
+
+.installed.cfg
+
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+#MacOS files
+.DS_Store
+
+#Bower
+static/bower_components
+
+#migrations
+projecthandler/migrations/
+sf_user/migrations/
+instancehandler/migrations
diff --git a/Dockerfile b/Dockerfile
new file mode 100644 (file)
index 0000000..56b0497
--- /dev/null
@@ -0,0 +1,24 @@
+FROM python:2.7
+
+WORKDIR /usr/src/app
+COPY . /usr/src/app
+
+RUN apt-get update
+RUN apt-get -y install npm
+RUN npm install -g bower
+RUN ln -s /usr/bin/nodejs /usr/bin/node
+RUN bower install --allow-root
+RUN pip install -r requirements.txt
+
+
+# delete the copy of the database inside the container (if exists)
+RUN rm -f db.sqlite3
+
+RUN python manage.py makemigrations sf_user projecthandler instancehandler vimhandler
+RUN python manage.py migrate
+
+RUN python manage.py shell -c "from projecthandler.osm_model import OsmProject; from sf_user.models import CustomUser; CustomUser.objects.create_superuser('admin', 'admin'); admin = CustomUser.objects.get(username='admin'); OsmProject.create_project('admin',admin,True, 'project admin','')"
+
+
+EXPOSE 80
+CMD ["python", "manage.py", "runserver", "0.0.0.0:80"]
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..c865b36
--- /dev/null
+++ b/README.md
@@ -0,0 +1,50 @@
+<!--<p align="center"><img height="80" src="https://github.com/superfluidity/RDCL3D/blob/master/code/static/assets/img/rdcl3dlogo0.png"/></p>
+-->
+
+# Lightweight GUI for OSM lightweight build 
+
+This project focuses on the implementation of a web GUI to interact with the Northbound API of OSM lightweigth build. 
+
+The project is based on ([RDCL 3D](https://github.com/superfluidity/RDCL3D)), a web framework for the design of NFV services and components. The framework allows editing,
+validating, visualizing the descriptors of services and components both textually and graphically.
+
+
+## Documentation & publications
+
+A publication on the background work (RDCL 3D) is available:
+
+S. Salsano, F. Lombardo, C. Pisa, P. Greto, N. Blefari-Melazzi,
+“RDCL 3D, a Model Agnostic Web Framework for the Design and Composition of NFV Services”,
+3rd IEEE International Workshop on Orchestration for Software Defined Infrastructures, O4SDI at IEEE NFV-SDN conference, Berlin, 6-8 November 2017 ([pdf on Arxiv](https://arxiv.org/pdf/1702.08242))
+
+## Community
+
+Follow 
+
+The mailing list [rdcl3d@googlegroups.com](mailto:rdcl3d@googlegroups.com) is available for architecture and design discussions,
+requests for help, features request, bug reports on RDCL 3D... To join the list, just send an email to [rdcl3d+subscribe@googlegroups.com](mailto:rdcl3d+subscribe@googlegroups.com) or [join with a gmail account](https://groups.google.com/forum/#!forum/rdcl3d)
+
+## Aknowledgements
+
+This work has been performed in the context of the project Superfluidity, which received funding from the European Union's Horizon 2020 research and innovation programme under grant agreement No. 671566.
+
+## Installation
+
+This project is included in the installer of OSM lightweight build
+
+
+## License
+
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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.
diff --git a/bower.json b/bower.json
new file mode 100644 (file)
index 0000000..136f061
--- /dev/null
@@ -0,0 +1,27 @@
+{
+  "name": "osm-lw-ui",
+  "authors": [
+    "Francesco Lombardo <flombardo@cnit.it>"
+  ],
+  "description": "OSM Light UI",
+  "main": "",
+  "license": "Apache-2.0",
+  "homepage": "https://osm.etsi.org",
+  "private": true,
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "resolutions": {
+    "jquery": "2 - 3"
+  },
+  "dependencies": {
+    "admin-lte": "^2.4.3",
+    "codemirror": "^5.36.0",
+    "d3": "^5.1.0",
+    "bootbox.js": "bootbox#^4.4.0"
+  }
+}
diff --git a/instancehandler/__init__.py b/instancehandler/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/instancehandler/admin.py b/instancehandler/admin.py
new file mode 100644 (file)
index 0000000..2e9690a
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.contrib import admin
+
+# Register your models here.
diff --git a/instancehandler/apps.py b/instancehandler/apps.py
new file mode 100644 (file)
index 0000000..2f49d31
--- /dev/null
@@ -0,0 +1,23 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.apps import AppConfig
+
+
+class InstancehandlerConfig(AppConfig):
+    name = 'instancehandler'
diff --git a/instancehandler/models.py b/instancehandler/models.py
new file mode 100644 (file)
index 0000000..21d5735
--- /dev/null
@@ -0,0 +1,21 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.db import models
+
+# Create your models here.
diff --git a/instancehandler/template/instance_list.html b/instancehandler/template/instance_list.html
new file mode 100644 (file)
index 0000000..1b0868d
--- /dev/null
@@ -0,0 +1,152 @@
+{% extends "base.html" %}
+{% load get %}
+{% load staticfiles %}
+
+
+
+{% block head_block %}
+    {{ block.super }}
+    <!-- Codemirror core CSS -->
+    <link rel="stylesheet" href="/static/bower_components/codemirror/lib/codemirror.css">
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/fold/foldgutter.css" />
+    <link rel="stylesheet" href="/static/bower_components/codemirror/theme/neat.css">
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/dialog/dialog.css">
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/display/fullscreen.css">
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+    {% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+    {{ block.super }}
+    <li><a href="{% url 'projects:instances:list' project_id=project_id type=type %}">Instances</a></li>
+{% endblock %}
+
+{% block content_body %}
+    {{ block.super }}
+    {% include 'modal/instance_create.html' %}
+    {% csrf_token %}
+    <div class="row">
+        <div class="col-md-12">
+
+            <div class="box">
+                <div class="box-header with-border">
+                    <h3 class="box-title">Instances</h3>
+                    <!--
+                    <div class="box-tools">
+                        <a href="javascript:newVimModal()" class="btn btn-block btn-primary btn-sm"><i
+                                class="fa fa-plus"></i><span> New VIM</span></a>
+                    </div>
+                    -->
+                </div>
+                <div class="box-body">
+                    <table id="instances_table" class="table table-bordered table-striped">
+                        <thead>
+                        <tr>
+                            <th>Id</th>
+                            <th>Name</th>
+                            <th>Nsd name</th>
+                            <th style="width:5%">Operational Status</th>
+                            <th style="width:5%">Config Status</th>
+                            <th>Detailed Status</th>
+                            <th >Actions</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {% for i in instances %}
+                            <tr>
+
+                                <td>{{ i|get:"_id" }}</td>
+                                <td>{{ i|get:"short-name" }}</td>
+                                <td>{{ i|get:"nsd-name-ref" }}</td>
+
+                                {% if i|get:"operational-status" == 'failed' %}
+                                    <td><span class="label label-danger">{{ i|get:"operational-status"  }}</span> </td>
+                                {% elif i|get:"operational-status" == 'init' %}
+                                     <td><span class="label label-warning">{{ i|get:"operational-status"  }}</span> </td>
+                                {% elif i|get:"operational-status" == 'running' %}
+                                     <td><span class="label label-success">{{ i|get:"operational-status"  }}</span> </td>
+                                {% else  %}
+                                    <td>{{ i|get:"operational-status"  }}</td>
+                                {% endif %}
+                                {% if i|get:"config-status" == 'failed' %}
+                                    <td><span class="label label-danger">{{ i|get:"config-status"  }}</span> </td>
+                                {% elif i|get:"config-status" == 'init' %}
+                                     <td><span class="label label-warning">{{ i|get:"config-status"  }}</span> </td>
+                                {% elif i|get:"config-status" == 'running' %}
+                                     <td><span class="label label-success">{{ i|get:"config-status"  }}</span> </td>
+                                {% elif i|get:"config-status" == 'configured' %}
+                                    <td><span class="label label-success">{{ i|get:"config-status"  }}</span> </td>
+                                {% else  %}
+                                    <td>{{ i|get:"config-status"  }}</td>
+                                {% endif %}
+                                <td style=" max-width:1px;overflow:hidden; white-space:nowrap; text-overflow:ellipsis;">{{ i|get:"detailed-status" }}</td>
+                                <td>
+                                    <div class="btn-group">
+                                        <button type="button" class="btn btn-default"
+                                                onclick="javascript:showInstanceDetails('{% url 'projects:instances:show' instance_id=i|get:'_id' project_id=project_id type=type %}')"
+                                                data-toggle="tooltip" data-placement="top" data-container="body" title="Show Info"><i
+                                                class="fa fa-info"></i>
+                                        </button>
+
+                                        <button type="button" class="btn btn-default"
+                                                onclick="javascript:deleteNs('{% url 'projects:instances:delete' instance_id=i|get:'_id' project_id=project_id type=type %}')"
+                                                data-toggle="tooltip" data-placement="top" data-container="body" title="Delete"><i
+                                                class="fa fa-trash-o"></i></button>
+
+                                            <button type="button" class="btn btn-default dropdown-toggle"
+                                                    data-toggle="dropdown" aria-expanded="false">Actions
+                                                <span class="fa fa-caret-down"></span></button>
+                                            <ul class="dropdown-menu">
+                                                <li><a href="#" onclick="javascript:performAction('{% url 'projects:instances:action' instance_id=i|get:'_id' project_id=project_id type=type %}')">
+                                                    <i class="fa fa-magic"></i>Exec NS Primitive</a></li>
+                                                <li><a href="#"> <i class="fa fa-list"></i>Active operations</a></li>
+                                            </ul>
+
+
+                                    </div>
+
+                                </td>
+
+
+                            </tr>
+                        {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+    </div>
+{% include 'modal/instance_show.html' %}
+{% include 'modal/instance_new_action.html' %}
+{% endblock %}
+
+{% block resource_block %}
+    {{ block.super }}
+    <!-- Utility JS -->
+
+    <script src="/static/bower_components/codemirror/lib/codemirror.js"></script>
+    <script src="/static/bower_components/codemirror/addon/fold/foldcode.js"></script>
+    <script src="/static/bower_components/codemirror/addon/fold/foldgutter.js"></script>
+    <script src="/static/bower_components/codemirror/addon/fold/brace-fold.js"></script>
+    <script src="/static/bower_components/codemirror/mode/javascript/javascript.js"></script>
+    <script src="/static/bower_components/codemirror/addon/search/searchcursor.js"></script>
+    <script src="/static/bower_components/codemirror/addon/search/search.js"></script>
+    <script src="/static/bower_components/codemirror/addon/dialog/dialog.js"></script>
+    <script src="/static/bower_components/codemirror/addon/display/autorefresh.js"></script>
+    <script src="/static/bower_components/codemirror/addon/edit/matchbrackets.js"></script>
+    <script src="/static/bower_components/codemirror/addon/edit/closebrackets.js"></script>
+    <script src="/static/bower_components/codemirror/addon/display/fullscreen.js"></script>
+    <script src="/static/bower_components/codemirror/keymap/sublime.js"></script>
+    <script src="/static/src/instancehandler/instance_list.js"></script>
+
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html" %}
+{% endblock %}
diff --git a/instancehandler/template/modal/instance_create.html b/instancehandler/template/modal/instance_create.html
new file mode 100644 (file)
index 0000000..c40ecb2
--- /dev/null
@@ -0,0 +1,55 @@
+<div class="modal" id="modal_new_instance" xmlns="http://www.w3.org/1999/html">
+    <div   class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" >New Instance</h4>
+            </div>
+             <form id="formCreateNS" action='{% url "projects:instances:create"  project_id=project_id%}' class="form-horizontal"
+                      method="post" enctype="multipart/form-data">
+                     {% csrf_token %}
+            <div class="modal-body" id="modal_new_instance_body" >
+                <div class="form-group">
+                    <label for="nsName" class="col-sm-3 control-label">Name</label>
+                        <div class="col-sm-6">
+                            <input class="form-control" id="nsName" name="nsName"
+                                   placeholder="Ns name" required>
+                        </div>
+                </div>
+                <div class="form-group">
+                    <label for="nsDescription" class="col-sm-3 control-label">Description</label>
+                        <div class="col-sm-6">
+                            <input class="form-control" id="nsDescription" name="nsDescription"
+                                   placeholder="Description" >
+                        </div>
+                </div>
+                <div class="form-group">
+                    <label for="nsdId" class="col-sm-3 control-label">Nsd Id</label>
+                        <div class="col-sm-6">
+                            <input class="form-control" id="nsdId" name="nsdId"
+                                   placeholder="Nsd Id" >
+                        </div>
+                </div>
+                <div class="form-group">
+                    <label for="vimAccountId" class="col-sm-3 control-label">Vim Account Id</label>
+                        <div class="col-sm-6">
+                            <select id="vimAccountId" class="js-example-basic form-control" name="vimAccountId">
+                            </select>
+                        </div>
+                </div>
+
+
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+                <button class="btn btn-primary"  data-loading-text="<i class='fa fa-circle-o-notch fa-spin'></i> Creating..." id="create_new_instance">Create</button>
+
+            </div>
+                 </form>
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
diff --git a/instancehandler/template/modal/instance_new_action.html b/instancehandler/template/modal/instance_new_action.html
new file mode 100644 (file)
index 0000000..6cf3059
--- /dev/null
@@ -0,0 +1,57 @@
+<div class="modal" id="modal_instance_new_action" xmlns="http://www.w3.org/1999/html">
+    <div   class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" >Perform Action</h4>
+            </div>
+            <form id="formActionNS" action="" class="form-horizontal" method="post" enctype="multipart/form-data">
+                     {% csrf_token %}
+            <div class="modal-body" id="modal_instance_new_action_body" >
+
+                    <div class="row">
+                        <label for="primitive" class="col-sm-2">Primitive:</label>
+                        <div class="col-sm-3">
+                            <input class="form-control input-sm" id="primitive" name="primitive" required>
+                        </div>
+
+                        <label for="vnf_member_index" class="col-sm-4">VNF Member index: </label>
+                            <div class="col-sm-2">
+                                <input class="form-control input-sm" id="vnf_member_index" name="vnf_member_index" required>
+                            </div>
+                    </div>
+
+                <div class="row">
+                    <h5 class="col-sm-4">Primitive parameters :</h5>
+
+                </div>
+
+                <div id="primitive_params_div">
+
+                    <div class="form-group">
+                        <label  class="col-sm-2">Name: </label>
+                            <div class="col-sm-3">
+                                <input name="primitive_params_name" class="form-control input-sm" >
+                            </div>
+                        <label class="col-sm-2">Value: </label>
+                            <div class="col-sm-3">
+                                <input name="primitive_params_value" class="form-control input-sm" >
+                            </div>
+                        <button type="button" class="btn btn-success btn-add btn-sm">+</button>
+                    </div>
+                </div>
+
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+                <button  class="btn btn-primary pull-right" >Execute</button>
+            </div>
+            </form>
+
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
diff --git a/instancehandler/template/modal/instance_show.html b/instancehandler/template/modal/instance_show.html
new file mode 100644 (file)
index 0000000..6df8bc1
--- /dev/null
@@ -0,0 +1,23 @@
+<div class="modal" id="modal_show_instance" xmlns="http://www.w3.org/1999/html">
+    <div   class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" >Instance details</h4>
+            </div>
+
+            <div class="modal-body" id="modal_show_instance_body" >
+                <textarea id="instance_view_json">
+                </textarea>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+
+            </div>
+
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
diff --git a/instancehandler/tests.py b/instancehandler/tests.py
new file mode 100644 (file)
index 0000000..79947e6
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.test import TestCase
+
+# Create your tests here.
diff --git a/instancehandler/urls.py b/instancehandler/urls.py
new file mode 100644 (file)
index 0000000..b6a9e33
--- /dev/null
@@ -0,0 +1,27 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.conf.urls import url
+from instancehandler import views
+
+urlpatterns = [
+    url(r'^(?P<type>[ns|vnf]+)/list/', views.list, name='list'),
+    url(r'^create/', views.create, name='create'),
+    url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/delete$', views.delete, name='delete'),
+    url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)/action', views.action, name='action'),
+    url(r'^(?P<type>[ns|vnf]+)/(?P<instance_id>[0-9a-z-]+)', views.show, name='show'),
+
+]
diff --git a/instancehandler/views.py b/instancehandler/views.py
new file mode 100644 (file)
index 0000000..0c9a8a4
--- /dev/null
@@ -0,0 +1,97 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.shortcuts import render, redirect
+from django.contrib.auth.decorators import login_required, permission_required
+from django.http import HttpResponse, JsonResponse
+import json
+import logging
+from lib.osm.osmclient.client import Client
+
+
+@login_required
+def list(request, project_id=None, type=None):
+    client = Client()
+    if type == 'ns':
+        result = client.ns_list()
+
+    return __response_handler(request, {'instances': result, 'type': 'ns', 'project_id': project_id}, 'instance_list.html')
+
+
+@login_required
+def create(request, project_id=None):
+    result = {}
+    ns_data = {
+        "nsName": request.POST.get('nsName', 'WithoutName'),
+        "nsDescription": request.POST.get('nsDescription', ''),
+        "nsdId": request.POST.get('nsdId', ''),
+        "vimAccountId": request.POST.get('vimAccountId', ''),
+        # "ssh-authorized-key": [
+        #     {
+        #         request.POST.get('key-pair-ref', ''): request.POST.get('keyValue', '')
+        #     }
+        # ]
+    }
+    print ns_data
+    client = Client()
+    result = client.ns_create(ns_data)
+    return __response_handler(request, result, 'projects:instances:list', to_redirect=True, type='ns', project_id=project_id)
+
+
+@login_required
+def action(request, project_id=None, instance_id=None, type=None):
+    result = {}
+    client = Client()
+
+    # result = client.ns_action(instance_id, action_payload)
+    primitive_param_keys = request.POST.getlist('primitive_params_name')
+    primitive_param_value = request.POST.getlist('primitive_params_value')
+    action_payload = {
+        "vnf_member_index": request.POST.get('vnf_member_index'),
+        "primitive": request.POST.get('primitive'),
+        "primitive_params": {k: v for k, v in zip(primitive_param_keys, primitive_param_value) if len(k) > 0}
+    }
+
+    result = client.ns_action(instance_id, action_payload)
+    return __response_handler(request, result, None, to_redirect=False, status=result['status'] )
+
+
+@login_required
+def delete(request, project_id=None, instance_id=None, type=None):
+    result = {}
+    client = Client()
+    result = client.ns_delete(instance_id)
+    print result
+    return __response_handler(request, result, 'projects:instances:list', to_redirect=True, type='ns', project_id=project_id)
+
+
+@login_required
+def show(request, project_id=None, instance_id=None, type=None):
+    # result = {}
+    client = Client()
+    result = client.ns_get(instance_id)
+    print result
+    return __response_handler(request, result)
+
+
+def __response_handler(request, data_res, url=None, to_redirect=None, *args, **kwargs):
+    raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',')
+    if 'application/json' in raw_content_types or url is None:
+        return JsonResponse(data_res, *args, **kwargs)
+    elif to_redirect:
+        return redirect(url, *args, **kwargs)
+    else:
+        return render(request, url, data_res)
diff --git a/lib/TopologyModels/example/example.yaml b/lib/TopologyModels/example/example.yaml
new file mode 100644 (file)
index 0000000..04633f5
--- /dev/null
@@ -0,0 +1,53 @@
+name: Example                              #Model name
+description: example                       #Model description
+version: 1.0                            #Model version
+designer: Pierluigi Greto               #Model designer
+nodes:                                   #List of nodes, with id and label used in the gui (We can add more fields)
+  examplenode1:
+    label: EXN1
+  examplenode2:
+    label: EXN2
+layer:                                  #List of Layers
+    exampleLayer:
+      nodes:                  #List of node to be visualized in the current layer
+        examplenode1:
+          addable:
+            callback: addNode
+          removable: true
+        examplenode1:
+          addable:
+            callback: addNode
+          removable: true
+      allowed_edges:                  #List of allowed edges between the layer's nodes
+        examplenode1:                        #Edge's source
+          destination:           #List of edge's destination with the list of controls callback id to call when there is a connections
+            examplenode1:
+              direct_edge: false
+              removable: true
+            examplenode2:
+              direct_edge: false
+              removable: true
+action:           #Action to show on rightclick on a node/link
+    rightclick:
+      node:
+        delete:
+          title: Delete
+          callback: deleteNode
+      link:
+        delete:
+          title: Delete
+          callback: deleteLink
+
+callback:                             #List of callbacks used
+  addNode:
+      file: example_controller.js
+      class: ExampleController
+  removeNode:
+      file: example_controller.js
+      class: ExampleController
+  addLink:
+      file: example_controller.js
+      class: ExampleController
+  removeLink:
+      file: example_controller.js
+      class: ExampleController
diff --git a/lib/TopologyModels/osm/osm.yaml b/lib/TopologyModels/osm/osm.yaml
new file mode 100644 (file)
index 0000000..59ac883
--- /dev/null
@@ -0,0 +1,187 @@
+name: Osm                            #Model name
+description: Osm                    #Model description
+version: 1.0                            #Model version
+designer: Francesco Lombardo              #Model designer
+nodes:                                  #List of nodes, with id and label used in the gui (We can add more fields)
+    vnf:
+        label: VNF
+    ns_vl:
+        label: VL
+    ns_cp:
+        label: SAP
+    vnf_vl:
+        label: intVL
+    vnf_vdu:
+        label: VDU
+    vnf_ext_cp:
+        label: ExtCP
+    vnf_vdu_cp:
+        label: VduCP
+layer:                                  #List of Layers
+    ns:
+        nodes:                  #List of node to be visualized in the current layer
+            vnf:
+                addable:
+                    callback: addVnf
+                removable:
+                    callback: removeNode
+                expands : vnf
+            ns_vl:
+                addable:
+                    callback: addNode
+                removable:
+                    callback: removeNode
+            ns_cp:
+                addable:
+                    callback: addNode
+                removable:
+                    callback: removeNode
+        allowed_edges:                  #List of allowed edges between the layer's nodes
+            vnf:                        #Edge's source
+                destination:            #List of edge's destination with the list of controls callback id to call when there is a connections
+                    ns_vl:
+                        callback: linkVnftoNsVl
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+                    ns_cp:
+                        callback: linkVnftoNsCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+            ns_vl:
+                destination:
+                    vnf:
+                        callback: linkVnftoNsVl
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+                    ns_cp:
+                        callback: nsCpExclusiveConnection
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+
+            ns_cp:
+                destination:
+                    vnf:
+                        callback: linkVnftoNsCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+                    ns_vl:
+                        callback: nsCpExclusiveConnection
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+        action:           #Action to show on rightclick all types of node/link
+            node:
+                addToCurrentVNFFG:
+                    title: Add to current VNFFG
+                    callback: addToCurrentVNFFG
+            link:
+
+    vnf:
+        nodes:                  #List of node to be visualized in the current layer
+            vnf_vl:
+                addable:
+                    callback: addNode
+                removable:
+                    callback: removeNode
+            vnf_vdu:
+                addable:
+                    callback: addVnfVdu
+                removable:
+                    callback: removeVnfVdu
+            vnf_ext_cp:
+                addable:
+                    callback: addNode
+                removable:
+                    callback: removeNode
+            vnf_vdu_cp:
+                addable:
+                    callback: addVnfVduCp
+                removable:
+                    callback: removeVnfVduCp
+        allowed_edges:                  #List of allowed edges between the layer's nodes
+            vnf_vl:                        #Edge's source
+                destination:            #List of edge's destination with the list of controls callback id to call when there is a connections
+                    vnf_vdu_cp:
+                        callback: linkVltoVduCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+                    vnf_ext_cp:
+                        callback: linkVnfVltoExpCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+            vnf_ext_cp:
+                destination:
+                    vnf_vl:
+                        callback: linkVnfVltoExpCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+            vnf_vdu_cp:
+                destination:
+                    vnf_vl:
+                        callback: linkVltoVduCp
+                        direct_edge: false
+                        removable:
+                            callback: removeLink
+
+
+
+callback:                             #List of callbacks used
+  chooseVnfExp:
+      file: osm_controller.js
+      class: OSMController
+  nsCpExclusiveConnection:
+      file: osm_controller.js
+      class: OSMController
+  getVduConnectedToVduCp:
+      file: osm_controller.js
+      class: OSMController
+  addVnf:
+      file: osm_controller.js
+      class: OSMController
+  addNode:
+      file: osm_controller.js
+      class: OSMController
+  addVnfVdu:
+      file: osm_controller.js
+      class: OSMController
+  addVnfVduCp:
+      file: osm_controller.js
+      class: OSMController
+  addLink:
+      file: osm_controller.js
+      class: OSMController
+  linkVnftoNsCp:
+      file: osm_controller.js
+      class: OSMController
+  linkVnftoNsVl:
+      file: osm_controller.js
+      class: OSMController
+  linkVltoVduCp:
+      file: osm_controller.js
+      class: OSMController
+  linkVnfVltoExpCp:
+      file: osm_controller.js
+      class: OSMController
+  removeNode:
+      file: osm_controller.js
+      class: OSMController
+  removeVnfVdu:
+      file: osm_controller.js
+      class: OSMController
+  removeVnfVduCp:
+      file: osm_controller.js
+      class: OSMController
+  removeLink:
+      file: osm_controller.js
+      class: OSMController
+  addToCurrentVNFFG:
+      file: osm_controller.js
+      class: OSMController
diff --git a/lib/__init__.py b/lib/__init__.py
new file mode 100644 (file)
index 0000000..bfcc6bf
--- /dev/null
@@ -0,0 +1 @@
+__all__ = ["Util", 'etsi']
diff --git a/lib/osm/__init__.py b/lib/osm/__init__.py
new file mode 100644 (file)
index 0000000..26d8656
--- /dev/null
@@ -0,0 +1 @@
+__all__ = ["osm_parser", "osm_rdcl_graph"]
\ No newline at end of file
diff --git a/lib/osm/osm_parser.py b/lib/osm/osm_parser.py
new file mode 100644 (file)
index 0000000..a596405
--- /dev/null
@@ -0,0 +1,89 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+import pyaml
+import yaml
+from lib.util import Util
+from lib.parser import Parser
+import logging
+import traceback
+import glob
+import os
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('OsmParser')
+
+class OsmParser(Parser):
+    """Parser methods for osm project type
+
+    """
+
+    def __init__(self):
+        super(OsmParser, self).__init__()
+    
+    @classmethod        
+    def importprojectdir(cls,dir_project, file_type):
+        """Imports all descriptor files under a given folder
+
+        this method is specific for Osm project type
+        """
+
+        project = {
+            'nsd':{},
+
+            'vnfd':{},
+
+            'positions': {}
+        }
+
+
+        for desc_type in project:
+            cur_type_path = os.path.join(dir_project, desc_type.upper())
+            log.debug(cur_type_path)
+            if os.path.isdir(cur_type_path):
+                for file in glob.glob(os.path.join(cur_type_path, '*.'+file_type)):
+                    if file_type == 'json':
+                        project[desc_type][os.path.basename(file).split('.')[0]] = Util.loadjsonfile(file)
+                    elif file_type == 'yaml':
+                        project[desc_type][os.path.basename(file).split('.')[0]] = Util.loadyamlfile(file)
+
+
+        for vertices_file in glob.glob(os.path.join(dir_project, '*.json')):
+            if os.path.basename(vertices_file) == 'vertices.json':
+                project['positions']['vertices'] = Util.loadjsonfile(vertices_file)
+
+        return project
+
+    @classmethod
+    def importprojectfiles(cls, file_dict):
+        """Imports descriptors (extracted from the new project POST)
+
+        The keys in the dictionary are the file types
+        """
+        project = {
+            'nsd':{},
+
+            'vnfd':{},
+
+        }
+        for desc_type in project:
+            if desc_type in file_dict:
+                files_desc_type = file_dict[desc_type]
+                for file in files_desc_type:
+                    project[desc_type][os.path.splitext(file.name)[0]] = json.loads(file.read())
+
+        return project
\ No newline at end of file
diff --git a/lib/osm/osm_rdcl_graph.py b/lib/osm/osm_rdcl_graph.py
new file mode 100644 (file)
index 0000000..91b798f
--- /dev/null
@@ -0,0 +1,51 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+import logging
+import copy
+from lib.rdcl_graph import RdclGraph
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('OsmRdclGraph')
+
+class OsmRdclGraph(RdclGraph):
+    """Operates on the graph representation used for the GUI graph views"""
+
+    def __init__(self):
+        pass
+
+
+    def build_graph_from_project(self, json_project, model={}):
+        """Creates a single graph for a whole project"""
+
+        #print "json_project ",json_project
+        graph_object = {
+            'vertices': [],
+            'edges': [],
+            'graph_parameters': {},
+            'model': model
+        }
+        try:
+            positions = json_project['positions'] if 'positions' in json_project else False
+            log.debug('build graph from project json')
+
+
+        except Exception as e:
+            log.exception('Exception in build_graph_from_project')
+            raise
+
+        return graph_object
diff --git a/lib/osm/osmclient/__init__.py b/lib/osm/osmclient/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/osm/osmclient/client.py b/lib/osm/osmclient/client.py
new file mode 100644 (file)
index 0000000..48c9815
--- /dev/null
@@ -0,0 +1,430 @@
+import requests
+import logging
+import json
+import tarfile
+import yaml
+import pyaml
+import StringIO
+from lib.util import Util
+import hashlib
+import os
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('helper.py')
+
+
+class Client(object):
+    def __init__(self, host=os.getenv('OSM_SERVER', "localhost"), so_port=9999, so_project='admin', ro_host=None, ro_port=9090, **kwargs):
+
+        self._user = 'admin'
+        self._password = 'admin'
+        # self._project = so_project
+        self._project = so_project
+        self._token_endpoint = 'admin/v1/tokens'
+        self._user_endpoint = 'admin/v1/users'
+
+        self._headers = {}
+        self._host = host
+
+        self._base_path = "https://{0}:{1}/osm".format(self._host, so_port)
+
+    def get_token(self):
+        postfields_dict = {'username': self._user,
+                           'password': self._password,
+                           'project-id': self._project}
+        token_url = "{0}/{1}".format(self._base_path, self._token_endpoint)
+        token = self._send_post(token_url, None, postfields_dict, headers={"Content-Type": "application/yaml", "accept": "application/json"})
+        if token is not None:
+            return token['id']
+        return None
+
+    def vim_list(self):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/admin/v1/vims".format(self._base_path)
+            return self._send_get(_url, headers=self._headers)
+
+    def vim_delete(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/admin/v1/vims/{1}".format(self._base_path, id)
+            return self._send_delete(_url, headers=self._headers)
+        return None
+
+    def vim_get(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/admin/v1/vims/{1}".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def vim_create(self, vim_data):
+        token = self.get_token()
+        headers = {}
+        if token:
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/json'
+            headers['accept'] = 'application/json'
+
+            _url = "{0}/admin/v1/vims".format(self._base_path)
+            return self._send_post(_url, headers=headers,
+                                  json=vim_data)
+        return None
+
+    def nsd_list(self):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/nsd/v1/ns_descriptors_content".format(self._base_path)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def nsd_get(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            #self._headers['accept'] = 'application/json'
+            _url = "{0}/nsd/v1/ns_descriptors/{1}/nsd".format(self._base_path,id)
+            return yaml.load(self._send_get(_url, headers=self._headers))
+        return None
+
+    def nsd_delete(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/nsd/v1/ns_descriptors_content/{1}".format(self._base_path, id)
+            return self._send_delete(_url, headers=self._headers)
+        return None
+
+    def _descriptor_update(self, tarf, data):
+        print tarf.getnames()
+        # extract the package on a tmp directory
+        tarf.extractall('/tmp')
+
+        for name in tarf.getnames():
+            if name.endswith(".yaml") or name.endswith(".yml"):
+                with open('/tmp/' + name, 'w') as outfile:
+                    yaml.safe_dump(data, outfile, default_flow_style=False)
+                break
+
+        tarf_temp = tarfile.open('/tmp/' + tarf.getnames()[0] + ".tar.gz", "w:gz")
+        # tarf_temp = tarfile.open("pippo.tar.gz", "w:gz")
+        print tarf_temp.getnames()
+        # tarf_temp.add('/tmp/'+tarf.getnames()[0])
+        for tarinfo in tarf:
+            # if tarinfo.name.startswith(tarf.getnames()[0]):
+            #    new_name = tarinfo.name[len(tarf.getnames()[0]):]
+            tarf_temp.add('/tmp/' + tarinfo.name, tarinfo.name, recursive=False)
+        print tarf_temp.getnames()
+        tarf_temp.close()
+        return tarf
+
+    def nsd_update(self, id, data):
+        token = self.get_token()
+        headers = {}
+        if token:
+            # get the package onboarded
+            tar_pkg = self.get_nsd_pkg(id)
+            tarf = tarfile.open(fileobj=tar_pkg)
+
+            tarf = self._descriptor_update(tarf, data)
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/gzip'
+            headers['accept'] = 'application/json'
+            headers['Content-File-MD5'] = self.md5(open('/tmp/' + tarf.getnames()[0] + ".tar.gz", 'rb'))
+            #headers['Content-File-MD5'] = self.md5(open("pippo.tar.gz", 'rb'))
+
+            _url = "{0}/nsd/v1/ns_descriptors/{1}/nsd_content".format(self._base_path, id)
+            return self._send_put(_url, headers=headers, data=open('/tmp/'+tarf.getnames()[0] + ".tar.gz", 'rb'))
+            #return self._send_put(_url, headers=headers, data=open("pippo.tar.gz", 'rb'))
+
+        return None
+
+    def nsd_onboard(self, package):
+        token = self.get_token()
+        headers = {}
+        if token:
+
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/gzip'
+            headers['accept'] = 'application/json'
+            with open('/tmp/'+package.name, 'wb+') as destination:
+                for chunk in package.chunks():
+                    destination.write(chunk)
+            headers['Content-File-MD5'] = self.md5(open('/tmp/'+package.name, 'rb'))
+
+            _url = "{0}/nsd/v1/ns_descriptors_content/".format(self._base_path)
+            return self._send_post(_url, headers=headers,
+                                  data=open('/tmp/'+package.name, 'rb'))
+        return None
+
+    def nsd_artifacts(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'text/plain'
+            _url = "{0}/nsd/v1/ns_descriptors/{1}/artifacts".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def ns_list(self):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/nslcm/v1/ns_instances_content".format(self._base_path)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def ns_create(self, ns_data):
+        token = self.get_token()
+        headers = {}
+        if token:
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/yaml'
+            headers['accept'] = 'application/json'
+
+            _url = "{0}/nslcm/v1/ns_instances_content".format(self._base_path)
+            return self._send_post(_url, headers=headers,
+                                  json=ns_data)
+        return None
+
+    def ns_get(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/json'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/nslcm/v1/ns_instances_content/{1}".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def ns_delete(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            #self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/nslcm/v1/ns_instances_content/{1}".format(self._base_path, id)
+            return self._send_delete(_url, headers=self._headers)
+        return None
+
+    def ns_action(self, id, action_payload):
+        token = self.get_token()
+        headers = {}
+        if token:
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/json'
+            headers['accept'] = 'application/json'
+
+            _url = "{0}/nslcm/v1/ns_instances/{1}/action".format(self._base_path, id)
+            return self._send_post(_url, headers=headers,
+                                   json=action_payload)
+        return None
+
+    def vnfd_list(self):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/vnfpkgm/v1/vnf_packages_content".format(self._base_path)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def vnfd_get(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            #self._headers['accept'] = 'application/yaml'
+            _url = "{0}/vnfpkgm/v1/vnf_packages/{1}/vnfd".format(self._base_path, id)
+            return yaml.load(self._send_get(_url, headers=self._headers))
+        return None
+
+    def vnfd_delete(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/json'
+            _url = "{0}/vnfpkgm/v1/vnf_packages_content/{1}".format(self._base_path, id)
+            return self._send_delete(_url, headers=self._headers)
+        return None
+
+    def vnfd_update(self, id, data):
+        token = self.get_token()
+        headers = {}
+        if token:
+            # get the package onboarded
+            tar_pkg = self.get_vnfd_pkg(id)
+            tarf = tarfile.open(fileobj=tar_pkg)
+
+            tarf = self._descriptor_update(tarf, data)
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/gzip'
+            headers['accept'] = 'application/json'
+            headers['Content-File-MD5'] = self.md5(open('/tmp/' + tarf.getnames()[0] + ".tar.gz", 'rb'))
+            # headers['Content-File-MD5'] = self.md5(open("pippo.tar.gz", 'rb'))
+
+            _url = "{0}/vnfpkgm/v1/vnf_packages/{1}/package_content".format(self._base_path, id)
+            return self._send_put(_url, headers=headers, data=open('/tmp/' + tarf.getnames()[0] + ".tar.gz", 'rb'))
+            # return self._send_put(_url, headers=headers, data=open("pippo.tar.gz", 'rb'))
+
+        return None
+
+    def vnfd_onboard(self, package):
+        token = self.get_token()
+        headers = {}
+        if token:
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/gzip'
+            headers['accept'] = 'application/json'
+            with open('/tmp/'+package.name, 'wb+') as destination:
+                for chunk in package.chunks():
+                    destination.write(chunk)
+            headers['Content-File-MD5'] = self.md5(open('/tmp/'+package.name, 'rb'))
+            _url = "{0}/vnfpkgm/v1/vnf_packages_content".format(self._base_path)
+            return self._send_post(_url, headers=headers,
+                                   data=open('/tmp/' + package.name, 'rb'))
+        return None
+    def vnf_packages_artifacts(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            self._headers['Content-Type'] = 'application/yaml'
+            _url = "{0}/vnfpkgm/v1/vnf_packages/{1}/artifacts".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def _upload_package(self, filename, package):
+        token = self.get_token()
+        headers = {}
+        if token:
+            headers['Authorization'] = 'Bearer {}'.format(token)
+            headers['Content-Type'] = 'application/gzip'
+            headers['Content-File-MD5'] = self.md5(package)
+            headers['accept'] = 'application/json'
+        return None
+
+    def _send_post(self, url, data=None, json=None, **kwargs):
+        try:
+            r = requests.post(url, data=data, json=json, verify=False, **kwargs)
+            #print r.text
+        except Exception as e:
+            log.exception(e)
+            #print "Exception during send POST"
+            return {'error': 'error during connection to agent'}
+        return Util.json_loads_byteified(r.text)
+
+    def _send_put(self, url, data=None, json=None, **kwargs):
+        try:
+            r = requests.put(url, data=data, json=json, verify=False, **kwargs)
+            print r.text
+        except Exception as e:
+            log.exception(e)
+            #print "Exception during send PUT"
+            return {'error': 'error during connection to agent'}
+
+        return r.json
+
+    def _send_get(self, url, params=None, **kwargs):
+        try:
+            r = requests.get(url, params=None, verify=False, stream=True, **kwargs)
+            #print r.headers
+        except Exception as e:
+            log.exception(e)
+            #print "Exception during send GET"
+            return {'error': 'error during connection to agent'}
+        if 'accept' in kwargs['headers']:
+            accept = kwargs['headers']['accept']
+            if accept == 'application/json':
+                #print "json"
+                return Util.json_loads_byteified(r.text)
+            elif accept == 'application/zip':
+                tarf =StringIO.StringIO(r.content)
+                #tarf = tarfile.open(fileobj=StringIO.StringIO(r.content))
+                # for tarinfo in tarf:
+                #     #print(tarinfo.name, "is", tarinfo.size, "bytes in size and is")
+                #     if tarinfo.isreg():
+                #         #print("a regular file.")
+                #     elif tarinfo.isdir():
+                #         #print("a directory.")
+                #     else:
+                #         #print("something else.")
+                return tarf
+            else:
+                return r.text
+        else:
+            return r.text
+
+    def _send_delete(self, url, params=None, **kwargs):
+        try:
+            r = requests.delete(url, params=None, verify=False, **kwargs)
+            len(r.content)
+            print r.text
+        except Exception as e:
+            log.exception(e)
+            print "Exception during send DELETE"
+            return {'error': 'error during connection to agent'}
+        return r.json
+
+    def md5(self, f):
+        hash_md5 = hashlib.md5()
+        for chunk in iter(lambda: f.read(1024), b""):
+            hash_md5.update(chunk)
+        return hash_md5.hexdigest()
+
+    def get_nsd_pkg(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            #self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/zip'
+            _url = "{0}/nsd/v1/ns_descriptors/{1}/nsd_content".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+    def get_vnfd_pkg(self, id):
+        token = self.get_token()
+        if token:
+            self._headers['Authorization'] = 'Bearer {}'.format(token)
+            #self._headers['Content-Type'] = 'application/yaml'
+            self._headers['accept'] = 'application/zip'
+            _url = "{0}/vnfpkgm/v1/vnf_packages/{1}/package_content".format(self._base_path, id)
+            return self._send_get(_url, headers=self._headers)
+        return None
+
+
+
+if __name__ == '__main__':
+
+
+    client = Client()
+    package = client.get_nsd_pkg('be489dfb-5f15-48c1-b693-67d830c591e5')
+    tarf = tarfile.open(fileobj=package)
+    tarf.extractall('/tmp')
+    yaml_object = yaml.safe_dump({}, default_flow_style=False)
+    yaml_file = open('/tmp/cirros_2vnf_ns/cirros_2vnf_nsd.yaml', 'w')
+    yaml_object = pyaml.dump(yaml_object, yaml_file, safe=True)
+    tarf_temp = tarfile.open(tarf.getnames()[0]+".tar.gz", "w:gz")
+
+    for tarinfo in tarf:
+        tarf_temp.add('/tmp/'+tarinfo.name, tarinfo.name)
+    tarf_temp.close()
+
+
+
diff --git a/lib/parser.py b/lib/parser.py
new file mode 100644 (file)
index 0000000..1b764d2
--- /dev/null
@@ -0,0 +1,46 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+import pyaml
+import yaml
+from lib.util import Util
+import logging
+import traceback
+import glob
+import os
+
+
+class Parser(object):
+    """Parser methods base class
+
+    """
+
+    def __init__(self):
+        pass
+    
+    @classmethod        
+    def importprojectdir(cls,dir_project, type):
+        """Imports all files under a given folder
+
+        Returns an empty project
+        """
+
+        project = {}
+        return project
+
+    def get_all_ns_descriptors(self, nsd_id, project_data):
+        raise NotImplementedError
\ No newline at end of file
diff --git a/lib/rdcl_graph.py b/lib/rdcl_graph.py
new file mode 100644 (file)
index 0000000..9d96251
--- /dev/null
@@ -0,0 +1,82 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+import logging
+import copy
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('RdclGraph')
+
+
+class RdclGraph(object):
+    """ Operates on the graph representation used for the GUI graph views """
+
+    node_t3d_base = {
+        'info': {
+            'property': {
+                'custom_label': '',
+            },
+            'type': '',
+            'group': []
+        }
+    }
+
+    def __init__(self):
+        pass
+
+    def add_link(self, source, target, view, group, graph_object, optional={}):
+        if (source is None) or (target is None):
+            return
+        edge_obj = {
+            'source': source,
+            'target': target,
+            'view': view,
+            'group': [group],
+
+        }
+
+        edge_obj.update(optional)
+        if edge_obj not in graph_object['edges']:
+            graph_object['edges'].append(edge_obj)
+
+    def add_node(self, id, type, group, positions, graph_object, optional={}):
+        if id is None:
+            return
+        node = next((x for x in graph_object['vertices'] if x['id'] == id), None)
+        if node is not None:
+            node['info']['group'].append(group)
+        else:
+            node = copy.deepcopy(self.node_t3d_base)
+            node['id'] = id
+            node['info']['type'] = type
+            if group is not None:
+                node['info']['group'].append(group)
+            if positions and id in positions['vertices'] and 'x' in positions['vertices'][id] and 'y' in positions['vertices'][id]:
+                node['fx'] = positions['vertices'][id]['x']
+                node['fy'] = positions['vertices'][id]['y']
+            node['info'].update(optional)
+            graph_object['vertices'].append(node)
+
+    def is_directed_edge(self, source_type=None, target_type=None, layer=None, model={}):
+        if source_type is None or target_type is None or layer is None:
+            return None
+        if layer in model['layer'] and 'allowed_edges' in model['layer'][layer]:
+            if source_type in model['layer'][layer]['allowed_edges'] and target_type in model['layer'][layer]['allowed_edges'][source_type]['destination']:
+                edge_pro = model['layer'][layer]['allowed_edges'][source_type]['destination'][target_type]
+                return edge_pro['direct_edge'] if 'direct_edge' in edge_pro else False
+
+        return None
diff --git a/lib/util.py b/lib/util.py
new file mode 100644 (file)
index 0000000..71fc36b
--- /dev/null
@@ -0,0 +1,198 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+import yaml
+import pyaml
+import logging
+import jsonschema
+import uuid 
+
+_lib_name = 'Util'
+
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('lib/util.py')
+
+
+class Util(object):
+
+    def __init__(self):
+        # logging.basicConfig(level=logging.DEBUG)
+        # self.log = logging.getLogger('UtilLogger')
+        pass
+
+
+    @classmethod
+    def json_load_byteified(cls, file_handle):
+        return cls._byteify(
+            json.load(file_handle, object_hook=cls._byteify),
+            ignore_dicts=True
+        )
+
+    @classmethod
+    def json_loads_byteified(cls, json_text):
+        return cls._byteify(
+            json.loads(json_text, object_hook=cls._byteify),
+            ignore_dicts=True
+        )
+
+    @classmethod
+    def _byteify(cls, data, ignore_dicts = False):
+        # if this is a unicode string, return its string representation
+        if isinstance(data, unicode):
+            return data.encode('utf-8')
+        # if this is a list of values, return list of byteified values
+        if isinstance(data, list):
+            return [ cls._byteify(item, ignore_dicts=True) for item in data ]
+        # if this is a dictionary, return dictionary of byteified keys and values
+        # but only if we haven't already byteified it
+        if isinstance(data, dict) and not ignore_dicts:
+            return {
+                cls._byteify(key, ignore_dicts=True): cls._byteify(value, ignore_dicts=True)
+                for key, value in data.iteritems()
+            }
+        # if it's anything else, return it in its original form
+        return data
+
+    @classmethod
+    def yaml2json(cls, object_yaml):
+        """Converts a yaml object into a json representation"""
+        log.debug('yaml2json')
+        return json.dumps(object_yaml, sort_keys=True, indent=2) if not object_yaml is None else None
+
+    @classmethod
+    def json2yaml(cls, object_json):
+        """Converts a json object into a yaml representation"""
+        log.debug('json2yaml')
+        return yaml.safe_dump(object_json, default_flow_style=False) if not object_json is None else None
+
+    @classmethod
+    def openfile(cls, filepath, mode='r', buffering=1):
+        """Returns an open file given a filepath
+
+        If the filepath is already an open file, returns into
+        Raises Exception
+        """
+
+        log.debug('reading file ' + filepath)
+        try:
+            if isinstance(filepath, file):
+                return filepath
+            else:
+                return open(filepath, mode, buffering)
+
+        except IOError as e:
+            log.exception('openfile', e)
+            raise
+
+    @classmethod
+    def loadyamlfile(cls, name):
+        """Returns a yaml object from a filename or an open file
+
+        Raises Exception
+        """
+
+        yaml_object = None
+        try:
+            if isinstance(name, file):
+                yaml_object = yaml.load(name)
+            else:
+                yaml_file = cls.openfile(name)
+                yaml_object = yaml.load(yaml_file)
+
+            return yaml_object
+        except Exception as e:
+            log.exception('Exception loadYamlFile', e)
+            raise
+
+    @classmethod
+    def loadjsonfile(cls, name):
+        """Returns a json object from a filename or an open file
+
+        Raises Exception
+        """
+
+        json_object = None
+        try:
+            #raise IOError('error from throws')
+            if isinstance(name, file):
+                json_object = json.load(name)
+            else:
+                # json_file = self.openfile(name)
+                json_file = cls.openfile(name)
+                json_object = json.load(json_file)
+
+            return json_object
+        except Exception as e:
+            log.exception('Exception loadJsonFile', e)
+            raise
+
+    @classmethod
+    def writejsonfile(cls, name, json_object):
+        """Writes the dump of a json obj to a filename or an open file
+
+        Raises Exception
+        """
+
+        try:
+            log.debug('writejsonfile ' + name)
+            if isinstance(name, file):
+                json_object = json.dump(json_object, name)
+            else:
+                json_file = cls.openfile(name, 'w')
+                json_object = json.dump(json_object, json_file,separators=(',',': '), indent=4)
+        except Exception as e:
+            log.exception('Exception writejsonfile', e)
+            raise
+
+    @classmethod
+    def writeyamlfile(cls, name, yaml_object):
+        """Writes the dump of a yaml obj to a filename or an open file
+
+        Raises Exception
+        """
+
+        try:
+            log.debug('writeyamlfile ' + name)
+            if isinstance(name, file):
+                yaml_object = pyaml.dump(yaml_object, name, safe=True)
+            else:
+                yaml_file = cls.openfile(name, 'w')
+                yaml_object = pyaml.dump(yaml_object, yaml_file, safe=True)
+        except Exception as e:
+            log.exception('Exception writeyamlfile')
+            raise
+
+    @classmethod
+    def validate_json_schema(cls, reference_schema, data):
+        """Validates a json data against a json schema
+
+        Raises Exception
+        """
+
+        try:
+            # schema = cls.loadjsonfile("lib/etsi/schemas/"+type_descriptor+".json")
+             #print 'type_descriptor : '+type_descriptor
+            jsonschema.validate(data, reference_schema)
+            return True
+        except Exception as e:
+            log.exception('Exception validate json schema', e)
+            return False
+
+    @classmethod
+    def get_unique_id(cls):
+        return uuid.uuid4().hex[:6].upper()
diff --git a/manage.py b/manage.py
new file mode 100755 (executable)
index 0000000..3f3c655
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sf_t3d.settings")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
diff --git a/projecthandler/__init__.py b/projecthandler/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/projecthandler/admin.py b/projecthandler/admin.py
new file mode 100644 (file)
index 0000000..d85d57a
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.contrib import admin
+
+# Register your models here.
diff --git a/projecthandler/apps.py b/projecthandler/apps.py
new file mode 100644 (file)
index 0000000..b71d1e7
--- /dev/null
@@ -0,0 +1,21 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.apps import AppConfig
+
+
+class ProjecthandlerConfig(AppConfig):
+    name = 'projecthandler'
diff --git a/projecthandler/management/__init__.py b/projecthandler/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/projecthandler/management/commands/__init__.py b/projecthandler/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/projecthandler/management/commands/delete_project_type.py b/projecthandler/management/commands/delete_project_type.py
new file mode 100644 (file)
index 0000000..d750f9f
--- /dev/null
@@ -0,0 +1,14 @@
+from django.core.management.base import BaseCommand, CommandError
+
+
+class Command(BaseCommand):
+    help = 'Delete a project type'
+
+    def handle(self, *args, **options):
+
+            try:
+                print 'delete project type'
+            except Exception:
+                raise CommandError('Error unable to delete a new project type')
+
+            self.stdout.write(self.style.SUCCESS('Project type successfully deleted'))
diff --git a/projecthandler/management/commands/new_project_type.py b/projecthandler/management/commands/new_project_type.py
new file mode 100644 (file)
index 0000000..7c9dce9
--- /dev/null
@@ -0,0 +1,14 @@
+from django.core.management.base import BaseCommand, CommandError
+
+
+class Command(BaseCommand):
+    help = 'Create a new project type'
+
+    def handle(self, *args, **options):
+
+            try:
+                print 'new project type'
+            except Exception:
+                raise CommandError('Error unable to create a new project type')
+
+            self.stdout.write(self.style.SUCCESS('New project type successfully created'))
diff --git a/projecthandler/models.py b/projecthandler/models.py
new file mode 100644 (file)
index 0000000..66fb7a5
--- /dev/null
@@ -0,0 +1,333 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.db import models
+from django.utils import timezone
+import jsonfield
+from StringIO import StringIO
+import zipfile
+import json
+import yaml
+from lib.util import Util
+from model_utils.managers import InheritanceManager
+import logging
+import os, shutil
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('models.py')
+
+project_types = {}
+
+
+class Project(models.Model):
+    """ Base class for project types
+
+    data_project stores a validated JSON representation of the project
+    get_dataproject() method returns the python dict representation of the project
+
+
+    """
+    owner = models.ForeignKey('sf_user.CustomUser', db_column='owner')
+    name = models.CharField(max_length=20, default='')
+    created_date = models.DateTimeField(default=timezone.now)
+    updated_date = models.DateTimeField(default=timezone.now, blank=True, null=True)
+    info = models.TextField(default='No info')
+    data_project = jsonfield.JSONField(default={})
+    """Stores a validated JSON representation of the project"""
+
+    validated = models.BooleanField(default=False)
+
+    #InheritanceManager
+    objects = InheritanceManager()
+   
+
+    @classmethod
+    def get_project_types(cls):
+        global project_types
+        return project_types        
+
+    @classmethod
+    def add_project_type(cls, type, my_class):
+        global project_types
+        project_types [type]= my_class
+
+
+    @classmethod
+    def create_project(cls, name, user, validated, info, data_project):
+        project = cls.objects.create(name=name, owner=user, validated=False, info=info,
+                                                       data_project=data_project)
+        return project
+
+    @classmethod
+    def get_graph_model(cls, file_path):
+        """Returns the model of the graph of the project type as a yaml object
+
+        Returns an empty dict if there is no file with the model
+        """
+        # file_path = GRAPH_MODEL_FULL_NAME
+        graph_model = {}
+        try:
+            graph_model = Util.loadyamlfile(file_path)
+        except Exception as e:
+            log.exception(e)
+            pass
+        return graph_model       
+
+    def get_type(self):
+        return "Base"
+
+    #@classmethod
+    def get_dataproject(self):
+        """ Return the python dict representation of the project data
+
+        """
+        #current_data = json.loads(self.data_project)
+        current_data = Util.json_loads_byteified(self.data_project)
+
+        return current_data
+
+    #@classmethod
+    def get_overview_data(self):
+        result = {
+            'owner': self.owner,
+            'name': self.name,
+            'updated_date': self.updated_date,
+            'info': self.info,
+            'validated': self.validated
+        }
+
+        return result
+
+    def set_data_project(self, new_data, validated):
+        self.data_project = new_data
+        self.set_validated(validated)
+        self.update()
+
+    def update(self):
+        self.updated_date = timezone.now()
+        self.save()
+
+    def __str__(self):
+        return self.name
+
+    def edit_graph_positions(self, positions):
+        # print positions
+        try:
+            current_data = json.loads(self.data_project)
+            if 'positions' not in current_data:
+                current_data['positions'] = {}
+            if 'vertices' not in current_data['positions']:
+                current_data['positions']['vertices'] = {}
+            if 'vertices' in positions:
+                current_data['positions']['vertices'].update(positions['vertices'])
+            self.data_project = current_data
+            self.update()
+            result = True
+        except Exception as e:
+            log.debug(e)
+            result = False
+        return result
+
+    def get_descriptors(self, type_descriptor):
+        """Returns all descriptors of a given type"""
+
+        try:
+            current_data = json.loads(self.data_project)
+            result = current_data[type_descriptor]
+        except Exception as e:
+            log.debug(e)
+            result = {}
+        return result
+
+    def get_descriptor(self, descriptor_id, type_descriptor):
+        """Returns a specific descriptor"""
+
+        try:
+            current_data = json.loads(self.data_project)
+            result = current_data[type_descriptor][descriptor_id]
+            print descriptor_id, type_descriptor, result
+        except Exception as e:
+            log.debug(e)
+            result = {}
+
+        return result
+
+    def delete_descriptor(self, type_descriptor, descriptor_id):
+        try:
+            log.debug('delete descriptor'+ descriptor_id + ' ' + type_descriptor)
+            current_data = json.loads(self.data_project)
+            del (current_data[type_descriptor][descriptor_id])
+            self.data_project = current_data
+            self.update()
+            result = True
+        except Exception as e:
+            log.debug(e)
+            result = False
+        return result
+
+    def clone_descriptor(self, type_descriptor, descriptor_id, new_id):
+        try:
+            current_data = json.loads(self.data_project)
+            descriptor = current_data[type_descriptor][descriptor_id]
+            new_descriptor = self.get_clone_descriptor(descriptor, type_descriptor, new_id)
+            current_data[type_descriptor][new_id] = new_descriptor
+            self.data_project = current_data
+            self.update()
+            result = True
+        except Exception as e:
+            log.debug(e)
+            result = False
+        return result
+
+    def edit_descriptor(self, type_descriptor, descriptor_id, new_data, data_type):
+        try:
+
+            ##FIXME questa parte va completamente rivista cosi' ha varie lacune
+            #log.info('editing ',+ descriptor_id + ' ' + type_descriptor + ' ' + data_type)
+            current_data = json.loads(self.data_project)
+            new_descriptor = new_data
+            if data_type == 'json':
+                new_descriptor = json.loads(new_data)
+            elif data_type == 'yaml':
+                yaml_object = yaml.load(new_data)
+                new_descriptor = json.loads(Util.yaml2json(yaml_object))
+            if type_descriptor != 'click' and type_descriptor != 'oshi' and type_descriptor !='cran':
+                reference_schema = self.get_json_schema_by_type(type_descriptor)
+                Util.validate_json_schema(reference_schema, new_descriptor)
+            current_data[type_descriptor][descriptor_id] = new_descriptor
+            self.data_project = current_data
+            self.update()
+            result = True
+        except Exception as e:
+            log.debug(e)
+            result = False
+        return result
+
+    def get_zip_archive(self):
+        in_memory = StringIO()
+        try:
+            current_data = json.loads(self.data_project)
+            zip = zipfile.ZipFile(in_memory, "w", zipfile.ZIP_DEFLATED)
+            for desc_type in current_data:
+                for current_desc in current_data[desc_type]:
+                    zip.writestr(current_desc + '.json', json.dumps(current_data[desc_type][current_desc]))
+
+            zip.close()
+        except Exception as e:
+            log.debug(e)
+
+        in_memory.flush()
+        return in_memory
+
+    def get_positions(self):
+        """Returns the positions of nodes"""
+        try:
+            current_data = json.loads(self.data_project)
+            positions = {}
+            if 'positions' in current_data:
+                positions = current_data['positions']
+        except Exception as e:
+            log.debug(e)
+
+        return positions
+
+    def get_deployment_descriptor(self, **kwargs):
+        """Returns the deployment descriptor"""
+        raise NotImplementedError
+
+    def get_node_overview(self, **kwargs):
+        """Returns the node overview"""
+        raise NotImplementedError
+
+    def get_all_ns_descriptors(self, nsd_id):
+        raise NotImplementedError
+
+    def translate_push_ns_on_repository(self, translator, nsd_id, repository, **kwargs):
+        raise NotImplementedError
+
+
+class ProjectStateless(Project):
+
+    def get_descriptors(self, type_descriptor):
+        """Returns all descriptors of a given type"""
+        raise NotImplementedError
+
+    def delete_descriptor(self, type_descriptor, descriptor_id):
+        raise NotImplementedError
+
+    def get_all_ns_descriptors(self, nsd_id):
+        pass
+
+    def translate_push_ns_on_repository(self, translator, nsd_id, repository, **kwargs):
+        pass
+
+    def get_deployment_descriptor(self, **kwargs):
+        pass
+
+    def get_node_overview(self, **kwargs):
+        pass
+
+    def get_dataproject(self):
+        raise NotImplementedError
+
+    def get_overview_data(self):
+        raise NotImplementedError
+
+class Repository(models.Model):
+    """ Repository
+    """
+    name = models.CharField(max_length=20, default='')
+    base_url = models.TextField(default='')
+    last_update = models.DateTimeField(default=timezone.now)
+    DIR_NAME = "/tmp/git_repo/"
+
+    def fetch_repository(self):
+        """
+        :return: git.remote.FetchInfo object
+        """
+        if os.path.isdir(self.DIR_NAME):
+            shutil.rmtree(self.DIR_NAME)
+
+        os.mkdir(self.DIR_NAME)
+        repo = git.Repo.init(self.DIR_NAME)
+        origin = repo.create_remote('origin', self.base_url)
+        origin.fetch()
+        fetch_info = origin.pull('master')[0]
+        return fetch_info
+
+    def push_repository(self, msg=None):
+        """
+        :param msg: Commit message
+        :return: git.remote.PushInfo object
+        """
+        repo = git.Repo.init(self.DIR_NAME)
+        origin = repo.remote('origin')
+        repo.git.add('--all')
+        repo.git.commit('-m \'[RDCL3D commit] ' + msg + '\'')
+        push_info = origin.push('master')[0]
+        return push_info
+
+    def to_json(self):
+        """
+        :return: JSON data of object
+        """
+        return {
+            'name': self.name,
+            'base_url': self.base_url.rstrip('\/'),
+            'last_update': self.last_update
+        }
diff --git a/projecthandler/osm_model.py b/projecthandler/osm_model.py
new file mode 100644 (file)
index 0000000..4776010
--- /dev/null
@@ -0,0 +1,328 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+import copy
+import json
+import os.path
+import yaml
+from lib.util import Util
+import logging
+from projecthandler.models import ProjectStateless
+
+from lib.osm.osm_parser import OsmParser
+from lib.osm.osm_rdcl_graph import OsmRdclGraph
+from lib.osm.osmclient.client import Client
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('OsmModel.py')
+
+
+PATH_TO_SCHEMAS = 'lib/osm/schemas/'
+PATH_TO_DESCRIPTORS_TEMPLATES = 'lib/osm/descriptor_template'
+DESCRIPTOR_TEMPLATE_SUFFIX = '.json'
+GRAPH_MODEL_FULL_NAME = 'lib/TopologyModels/osm/osm.yaml'
+EXAMPLES_FOLDER = 'usecases/OSM/'
+
+
+class OsmProject(ProjectStateless):
+    """Osm Project class
+    The data model has the following descriptors:
+        # descrtiptor list in comment #
+
+    """
+
+    def get_descriptors(self, type_descriptor):
+        """Returns all descriptors of a given type"""
+        log.debug("Get %s descriptors", type_descriptor)
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                result = client.nsd_list()
+
+            elif type_descriptor == 'vnfd':
+                result = client.vnfd_list()
+
+        except Exception as e:
+            log.exception(e)
+            result = {}
+        return result
+
+    def get_descriptor(self, descriptor_id, type_descriptor):
+        """Returns a specific descriptor"""
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                result = client.nsd_get(descriptor_id)
+                print result
+            elif type_descriptor == 'vnfd':
+                result = client.vnfd_get(descriptor_id)
+
+                print result
+        except Exception as e:
+            log.exception(e)
+            result = {}
+
+        return result
+
+    @classmethod
+    def data_project_from_files(cls, request):
+
+        file_dict = {}
+        for my_key in request.FILES.keys():
+            file_dict[my_key] = request.FILES.getlist(my_key)
+
+        log.debug(file_dict)
+
+        data_project = OsmParser.importprojectfiles(file_dict)
+
+        return data_project
+
+    @classmethod
+    def data_project_from_example(cls, request):
+        osm_id = request.POST.get('example-osm-id', '')
+        data_project = OsmParser.importprojectdir(EXAMPLES_FOLDER + osm_id + '/JSON', 'yaml')
+        return data_project
+
+    @classmethod
+    def get_example_list(cls):
+        """Returns a list of directories, in each directory there is a project osm"""
+
+        path = EXAMPLES_FOLDER
+        dirs = [d for d in os.listdir(path) if os.path.isdir(os.path.join(path, d))]
+        return {'osm': dirs}
+
+    @classmethod
+    def get_new_descriptor(cls, descriptor_type, request_id):
+
+        json_template = cls.get_descriptor_template(descriptor_type)
+
+        return json_template
+
+    @classmethod
+    def get_descriptor_template(cls, type_descriptor):
+        """Returns a descriptor template for a given descriptor type"""
+
+        try:
+            schema = Util.loadjsonfile(os.path.join(PATH_TO_DESCRIPTORS_TEMPLATES, type_descriptor + DESCRIPTOR_TEMPLATE_SUFFIX))
+            return schema
+        except Exception as e:
+            log.exception(e)
+            return False
+
+    @classmethod
+    def get_clone_descriptor(cls, descriptor, type_descriptor, new_descriptor_id):
+        new_descriptor = copy.deepcopy(descriptor)
+
+        return new_descriptor
+
+    def get_type(self):
+        return "osm"
+
+    def __str__(self):
+        return self.name
+
+    def get_overview_data(self):
+        current_data = json.loads(self.data_project)
+        client = Client()
+        nsd = client.nsd_list()
+        vnfd = client.vnfd_list()
+        ns = client.ns_list()
+        result = {
+            'owner': self.owner.__str__(),
+            'name': self.name,
+            'updated_date': self.updated_date.strftime('%Y-%m-%d %H:%M'),
+            'info': self.info,
+            'type': 'osm',
+            'nsd': len(nsd) if nsd else 0,
+            'vnfd': len(vnfd) if vnfd else 0,
+            'ns': len(ns) if ns else 0,
+            'validated': self.validated
+        }
+
+        return result
+
+    def get_graph_data_json_topology(self, descriptor_id):
+        rdcl_graph = OsmRdclGraph()
+        project = self.get_dataproject()
+        topology = rdcl_graph.build_graph_from_project(project,
+                                                     model=self.get_graph_model(GRAPH_MODEL_FULL_NAME))
+        return json.dumps(topology)
+
+    def create_descriptor(self, descriptor_name, type_descriptor, new_data, data_type, file_uploaded):
+        """Creates a descriptor of a given type from a json or yaml representation
+
+        Returns the descriptor id or False
+        """
+        log.debug('Create descriptor')
+
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                result = client.nsd_onboard(file_uploaded)
+            elif type_descriptor == 'vnfd':
+                result = client.vnfd_onboard(file_uploaded)
+
+            else:
+                log.debug('Create descriptor: Unknown data type')
+                return False
+
+        except Exception as e:
+            log.exception(e)
+            result = False
+        return result
+
+    def delete_descriptor(self, type_descriptor, descriptor_id):
+        log.debug('Delete descriptor')
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                result = client.nsd_delete(descriptor_id)
+            elif type_descriptor == 'vnfd':
+                result = client.vnfd_delete(descriptor_id)
+
+            else:
+                log.debug('Create descriptor: Unknown data type')
+                return False
+
+        except Exception as e:
+            log.exception(e)
+            result = False
+        return result
+
+    def edit_descriptor(self, type_descriptor, descriptor_id, new_data, data_type):
+        log.debug("Edit descriptor")
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                if data_type == 'yaml':
+                    new_data = yaml.load(new_data)
+                elif data_type == 'json':
+                    new_data = json.loads(new_data)
+                result = client.nsd_update(descriptor_id, new_data)
+            elif type_descriptor == 'vnfd':
+                if data_type == 'yaml':
+                    new_data = yaml.load(new_data)
+                elif data_type == 'json':
+                    new_data = json.loads(new_data)
+                result = client.vnfd_update(descriptor_id, new_data)
+
+            else:
+                log.debug('Create descriptor: Unknown data type')
+                return False
+        except Exception as e:
+            log.exception(e)
+            result = False
+        return result
+
+    def get_package_files_list(self, type_descriptor, descriptor_id):
+        try:
+            client = Client()
+            if type_descriptor == 'nsd':
+                result = client.nsd_artifacts(descriptor_id)
+            elif type_descriptor == 'vnfd':
+                result = client.vnf_packages_artifacts(descriptor_id)
+            else:
+                return False
+            result = yaml.load(result)
+            print result
+        except Exception as e:
+            log.exception(e)
+            result = False
+        print result
+        return result
+
+    def set_validated(self, value):
+        self.validated = True if value is not None and value == True else False
+
+    def get_add_element(self, request):
+        result = False
+
+        return result
+
+    def get_remove_element(self, request):
+        result = False
+
+        return result
+
+    def get_add_link(self, request):
+
+        result = False
+
+        return result
+
+    def get_remove_link(self, request):
+        result = False
+
+        return result
+
+    def create_ns(self, descriptor_type, descriptor_id, data_ns):
+        try:
+            client = Client()
+            if descriptor_type == 'nsd':
+                result = client.ns_create( data_ns)
+            else:
+                return False
+
+        except Exception as e:
+            log.exception(e)
+            result = False
+        print result
+        return result
+
+    def download_pkg(self, project, descriptor_id, descriptor_type):
+        try:
+            client = Client()
+            if descriptor_type == 'nsd':
+                result = client.get_nsd_pkg(descriptor_id)
+            elif descriptor_type == 'vnfd':
+                result = client.get_vnfd_pkg(descriptor_id)
+            else:
+                return False
+
+        except Exception as e:
+            log.exception(e)
+            result = False
+        print result
+        return result
+
+    def get_available_nodes(self, args):
+        """Returns all available node """
+        log.debug('get_available_nodes')
+        try:
+            result = []
+            #current_data = json.loads(self.data_project)
+            model_graph = self.get_graph_model(GRAPH_MODEL_FULL_NAME)
+            for node in model_graph['layer'][args['layer']]['nodes']:
+
+                current_data = {
+                    "id": node,
+                    "category_name": model_graph['nodes'][node]['label'],
+                    "types": [
+                        {
+                            "name": "generic",
+                            "id": node
+                        }
+                    ]
+                }
+                result.append(current_data)
+
+            #result = current_data[type_descriptor][descriptor_id]
+        except Exception as e:
+            log.debug(e)
+            result = []
+        return result
diff --git a/projecthandler/template/project/descriptor/descriptor_view_base.html b/projecthandler/template/project/descriptor/descriptor_view_base.html
new file mode 100644 (file)
index 0000000..3635d66
--- /dev/null
@@ -0,0 +1,131 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+
+
+    <!-- Codemirror core CSS -->
+    <link rel="stylesheet" href="/static/bower_components/codemirror/lib/codemirror.css" >
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/fold/foldgutter.css"  />
+    <link rel="stylesheet" href="/static/bower_components/codemirror/theme/neat.css" >
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/dialog/dialog.css" >
+    <link rel="stylesheet" href="/static/bower_components/codemirror/addon/display/fullscreen.css" >
+
+{% endblock %}
+
+
+
+
+{% block content_body %}
+       {{ block.super }}
+<div class="row">
+    <div class="col-md-12">
+    <div class="nav-tabs-custom">
+    <ul class="nav nav-tabs">
+        {% block tab_pane_button_list %}
+
+        {% endblock %}
+
+        {% block nav_buttons_list %}
+
+        {% endblock %}
+
+    </ul>
+    <div class="tab-content">
+        {% block tab_pane_list %}
+
+        {% endblock %}
+    </div>
+    <!-- /.tab-content -->
+
+
+
+
+</div>
+
+    </div>
+</div>
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+
+    <script src="/static/bower_components/codemirror/lib/codemirror.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/fold/foldcode.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/fold/foldgutter.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/fold/brace-fold.js" ></script>
+    <script src="/static/bower_components/codemirror/mode/javascript/javascript.js" ></script>
+    <script src="/static/bower_components/codemirror/mode/yaml/yaml.js" ></script>
+    <script src="/static/bower_components/codemirror/mode/markdown/markdown.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/search/searchcursor.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/search/search.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/dialog/dialog.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/display/autorefresh.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/edit/matchbrackets.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/edit/closebrackets.js" ></script>
+    <script src="/static/bower_components/codemirror/addon/display/fullscreen.js" ></script>
+    <script src="/static/bower_components/codemirror/keymap/sublime.js" ></script>
+
+
+    <script>
+
+        var json_editor_settings = {
+            mode: "javascript",
+            showCursorWhenSelecting: true,
+            autofocus: true,
+            lineNumbers: true,
+            lineWrapping: true,
+            foldGutter: true,
+            gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+            autoCloseBrackets: true,
+            matchBrackets: true,
+            extraKeys: {
+                "F11": function (cm) {
+                    cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+                },
+                "Esc": function (cm) {
+                    if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+                },
+                "Ctrl-Q": function (cm) {
+                    cm.foldCode(cm.getCursor());
+                }
+            },
+            theme: "neat",
+            keyMap: "sublime"
+        };
+
+        var yaml_editor_settings = {
+            mode: "yaml",
+            showCursorWhenSelecting: true,
+            autofocus: true,
+            autoRefresh: true,
+            lineNumbers: true,
+            lineWrapping: true,
+            foldGutter: true,
+            gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+            autoCloseBrackets: true,
+            matchBrackets: true,
+            extraKeys: {
+                "F11": function (cm) {
+                    cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+                },
+                "Esc": function (cm) {
+                    if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+                },
+                "Ctrl-Q": function (cm) {
+                    cm.foldCode(cm.getCursor());
+                }
+            },
+            theme: "neat",
+            keyMap: "sublime"
+        };
+
+
+        function goToGraph() {
+            window.location.href = '/projects/{{project_id}}/graph?type={{descriptor_type}}&id={{descriptor_id}}'
+        }
+    </script>
+{% endblock %}
diff --git a/projecthandler/template/project/descriptor/modal/choose_node_id.html b/projecthandler/template/project/descriptor/modal/choose_node_id.html
new file mode 100644 (file)
index 0000000..3bfea9e
--- /dev/null
@@ -0,0 +1,27 @@
+<div class="modal" id="modal_choose_node_id" xmlns="http://www.w3.org/1999/html">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" id="modal_chooser_title_add_node">Add node</h4>
+            </div>
+            <div class="modal-body">
+                <div id="div_chose_id">
+                    <label>Choose an id </label>
+                    <input name="input_choose_node_id" id="input_choose_node_id" type="text" size="40" maxlength="20"/>
+                </div>
+                {% block content_body %}
+
+                {% endblock %}
+            </div>
+
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+                <button type="button" class="btn btn-primary" id="save_choose_node_id">Add</button>
+            </div>
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
\ No newline at end of file
diff --git a/projecthandler/template/project/modal/modal_keyboard_info_base.html b/projecthandler/template/project/modal/modal_keyboard_info_base.html
new file mode 100644 (file)
index 0000000..1986d5d
--- /dev/null
@@ -0,0 +1,37 @@
+<div id="modalTopologyInfoButton" class="modal fade" tabindex="-1" role="dialog">
+       <div class="modal-dialog ">
+               <div class="modal-content">
+                       <div class="modal-header">
+
+                               <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                                       <span aria-hidden="true">&times;</span>
+                               </button>
+                               <h4 class="modal-title">Keyboard shortcuts</h4>
+                       </div>
+                       <div class="modal-body">
+                <ul>
+
+                    <li>
+                        <p>Create edge: Select the first vertex by clicking on it. <span class="help-key">Shift</span> + <span class="help-key">left-click</span> on another vertex (different than the selected one).</p>
+                    </li>
+                    <li>
+                        <p> <span class="help-key">Delete</span> Delete selected nodes or link.</p>
+                    </li>
+
+                    <li>
+                        <p>Switch to automatic node positioning: Play/Pause</p>
+                    </li>
+                                       {% block left_sidebar %}
+
+                                       {% endblock %}
+                </ul>
+                       </div>
+                       <div class="modal-footer">
+
+                       </div>
+               </div>
+               <!-- /.modal-content -->
+       </div>
+       <!-- /.modal-dialog -->
+</div>
+<!-- /.modal -->
\ No newline at end of file
diff --git a/projecthandler/template/project/new_project.html b/projecthandler/template/project/new_project.html
new file mode 100644 (file)
index 0000000..44fb435
--- /dev/null
@@ -0,0 +1,129 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+{{ block.super }}
+    <link rel="stylesheet" href="{% static "bower_components/admin-lte/plugins/select2/select2.css" %}">
+
+    <style type="text/css">
+
+  input:required:invalid, input:focus:invalid {
+
+    background-position: right top;
+    background-repeat: no-repeat;
+  }
+  input:required:valid {
+
+    background-position: right top;
+    background-repeat: no-repeat;
+  }
+
+</style>
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'left_sidebar_base.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+{{ block.super }}
+<li><a href='{% url "projects:new_project" %}'>New Project</a></li>
+{% endblock %}
+
+{% block content_body %}
+{{ block.super }}
+<div class="row">
+    <div class="col-md-12">
+        <!-- general form elements -->
+        <div class="box box-primary">
+            <div class="box-header with-border">
+                <h3 class="box-title">New Project</h3>
+            </div>
+            <!-- /.box-header -->
+            <!-- form start -->
+            <form role="form" method="post" enctype="multipart/form-data">
+                {% csrf_token %}
+                <div class="box-body">
+                    <div class="row">
+                        <div class="form-group col-xs-3">
+                            <p for="typeButtonsSelect">Project Type</p>
+                            <input type="hidden" class="form-control input-sm" name="type" id="projectType"
+                                    required>
+                            <select id="select_type" class="js-example-basic-single form-control" style="width: 75%">
+
+                            </select>
+
+                        </div>
+                        <div class="form-group col-xs-4">
+                            <p for="projectName">Project Name</p>
+                            <input type="text" class="form-control input-sm" name="name" id="projectName"
+                                   placeholder="Enter Project name" required>
+                        </div>
+                        <div class="form-group col-xs-4">
+                            <p for="projectInfo">Info</p>
+                            <input type="text" class="form-control input-sm" name="info" id="projectInfo"
+                                   placeholder="Short Description">
+                        </div>
+
+
+                    </div>
+                    <div class="row">
+                        <div id="startGroup" class="form-group col-xs-5" style="display: none;">
+
+
+                            <div id="startButtonsSelect" class="btn-group" data-toggle="buttons">
+                                <label id="s-scratch" class="btn btn-primary active">
+                                    <input id="scratch" type="radio" name="startfrom" autocomplete="off" value="scratch">
+                                     Empty project
+                                </label>
+                                <label id="s-example" class="btn btn-primary">
+                                    <input type="radio" name="startfrom" autocomplete="off" value="example">
+                                     Example project
+                                </label>
+                                <label id="s-files" class="btn btn-primary">
+                                    <input type="radio" name="startfrom" autocomplete="off" value="files">
+                                     Load files
+                                </label>
+
+
+                            </div>
+                        </div>
+                    </div>
+                    <div id="typeStartContainer" class="row">
+                         <div class="form-group col-xs-8">
+                        {% autoescape off %}{{ type_container_template }}{% endautoescape %}
+                        </div>
+                    </div>
+
+                    <!-- /.box-body -->
+                </div>
+                <div class="box-footer">
+                    <button id="createButton" type="submit" class="btn btn-primary" disabled>Create</button>
+                </div>
+            </form>
+        </div>
+        <!-- /.box -->
+
+    </div>
+</div>
+{% endblock %}
+
+{% block resource_block %}
+{{ block.super }}
+    <!-- Select2 -->
+    <script src="{% static "bower_components/admin-lte/plugins/select2/select2.full.min.js" %}"></script>
+
+    <script src="{% static "src/projecthandler/new_project.js" %}"></script>
+<script>
+{% if data_type_selector %}
+var data_type_selector ={{ data_type_selector |safe }};
+{% endif %}
+
+{% if type_example_files %}
+var type_example_files = {{type_example_files | safe}};
+{% endif %}
+
+</script>
+{% endblock %}
+
diff --git a/projecthandler/template/project/osm/descriptor/descriptor_new.html b/projecthandler/template/project/osm/descriptor/descriptor_new.html
new file mode 100644 (file)
index 0000000..d257d7f
--- /dev/null
@@ -0,0 +1,155 @@
+{% extends "descriptor/descriptor_view_base.html" %}
+
+{% load staticfiles %}
+{% block head_base %}
+    {% with skin_css="AdminLTE/dist/css/skins/skin-purple.min.css"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block body %}
+    {% with skin="purple"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+{% block logo_sidebar %}
+    {% with logo_mini="assets/img/osm_small_logo.png" logo="assets/img/OSM-logo.png"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block title_header_big %}
+{{ block.super }}
+Onboard new {{ descriptor_type }} package
+{% endblock %}
+
+{% block left_sidebar %}
+{% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+{{ block.super }}
+<li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview_data.name}}</a></li>
+<li><a href="{% url 'projects:open_project' project_id=project_id %}descriptors/{{descriptor_type}}">{{ descriptor_type }}  Descriptors</a></li>
+<li>Create Descriptor</li>
+{% endblock %}
+
+{% block tab_pane_button_list %}
+{{ block.super }}
+<li id="file_li" class="active"><a href="#upload" data-toggle="tab"><i class="fa fa-upload"></i> Onboard Package</a></li>
+{% endblock %}
+
+{% block nav_buttons_list %}
+{{ block.super }}
+<li class="pull-right">
+    <button id="cancel" type="button" class="btn btn-block btn-primary btn-sm" onclick="cancel(this.id)"><i
+            class="fa fa-times"></i> Cancel
+    </button>
+</li>
+{% endblock %}
+
+{% block tab_pane_list %}
+{{ block.super }}
+
+<div class="tab-pane active" id="upload">
+
+    <h4>Drag and drop file below</h4>
+    <div class="upload-drop-zone" id="drop-zone">
+            Just drag and drop files here
+    </div>
+</div>
+{% endblock %}
+
+
+{% block resource_block %}
+{{ block.super }}
+
+<script>
+
+    var dropZone = document.getElementById('drop-zone');
+    dropZone.ondrop = function(e) {
+        e.preventDefault();
+        this.className = 'upload-drop-zone';
+        create(e.dataTransfer.files, true);
+    }
+
+    dropZone.ondragover = function() {
+        this.className = 'upload-drop-zone drop';
+        return false;
+    }
+
+    dropZone.ondragleave = function() {
+        this.className = 'upload-drop-zone';
+        return false;
+    }
+
+
+
+
+        function create(fs, dropzone){
+            var id = $('.nav-tabs .active').attr('id');
+            if (dropzone) id = 'file_li'
+            var type, text ;
+            var data = new FormData();
+            switch(id) {
+
+                case 'file_li':
+                    type = 'file'
+
+                    var files = dropzone ? fs : document.getElementById('js-upload-files').files;
+                    if (!files || !files.length) {
+                      files = document.getElementById('drop-zone').files
+                      if(!files || !files.length){
+                        alert("Seletc a file")
+                        return
+                      }
+                    }
+                    console.log(files[0])
+                    var patt1 = /\.([0-9a-z]+)(?:[\?#]|$)/i;
+                    console.log(files[0].name.match(patt1));
+                    var extension = files[0].name.substr(files[0].name.lastIndexOf('.') + 1)
+                    console.log(extension)
+                    if(!(extension == 'gz' )){
+                        alert("The file must be .tar.gz");
+                        return
+                    }
+
+                     data.append('file', files[0]);
+                    break;
+            }
+            data.append('csrfmiddlewaretoken','{{csrf_token}}');
+            data.append('type', type);
+            data.append('text', text);
+            data.append('id', '{{descriptor_id}}');
+            console.log(text);
+            $.ajax({
+                    url: "new",
+                    type: 'POST',
+                    data: data,
+                    cache: false,
+                    contentType: false,
+                    processData: false,
+                    success: function(result) {
+                        console.log(result)
+
+                        window.location.href="/projects/{{project_id}}/descriptors/{{descriptor_type}}"
+
+                    },
+                    error: function(result) {
+                        showAlert(result);
+                    }
+                });
+        }
+
+    function cancel(id){
+        window.location.href="/projects/{{project_id}}/descriptors/{{descriptor_type}}"
+    }
+
+
+
+</script>
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html" %}
+{% endblock %}
diff --git a/projecthandler/template/project/osm/descriptor/descriptor_view.html b/projecthandler/template/project/osm/descriptor/descriptor_view.html
new file mode 100644 (file)
index 0000000..5a082de
--- /dev/null
@@ -0,0 +1,126 @@
+{% extends "descriptor/descriptor_view_base.html" %}
+
+{% load staticfiles %}
+
+
+{% block title_header_big %}
+{{ block.super }}
+Edit {{ descriptor_type|upper }} Descriptor
+{% endblock %}
+
+
+{% block left_sidebar %}
+{% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+{{ block.super }}
+<li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview_data.name}}</a></li>
+<li><a href="{% url 'projects:open_project' project_id=project_id %}descriptors/{{descriptor_type}}">{{ descriptor_type }} Descriptors</a></li>
+<li><a>{{descriptor_id}}</a></li>
+{% endblock %}
+
+{% block tab_pane_button_list %}
+{{ block.super }}
+<li class="active" id="yaml_li"><a href="#yaml" data-toggle="tab"><i class="fa fa-file-code-o"></i> YAML</a></li>
+<li id="json_li"><a href="#json" data-toggle="tab"><i class="fa fa-file-code-o"></i> JSON</a></li>
+{% endblock %}
+
+{% block nav_buttons_list %}
+    {{ block.super }}
+    <li class="pull-right"><button id="save" type="button" class="btn btn-block btn-primary btn-sm"  onclick="update(this.id)" ><i class="fa fa-save"></i> Update</button></li>
+    <li class="pull-right"><button id="save_show_graph" type="button" class="btn btn-block btn-primary btn-sm"  onclick="update(this.id)" disabled><i class="fa fa-save"></i> Update and Show Graph</button></li>
+    <li class="pull-right"><button type="button" class="btn btn-block btn-primary btn-sm"  onclick="goToGraph()" disabled><i class="fa fa-sitemap"></i> Show Graph</button></li>
+{% endblock %}
+
+{% block tab_pane_list %}
+{{ block.super }}
+
+<!-- /.tab-pane -->
+<div class="active tab-pane" id="yaml">
+    <textarea id="code_editor_yaml">
+    </textarea>
+</div>
+<!-- /.tab-pane-->
+
+<div class="tab-pane" id="json">
+    <textarea id="code_editor_json">
+    </textarea>
+</div>
+
+{% endblock %}
+
+{% block resource_block %}
+{{ block.super }}
+
+
+<script>
+    var editorJSON;
+    var editorYaml;
+
+    $(document).ready(function () {
+        //var cmjsoneditor = CodeMirror.fromTextArea(document.getElementById("code_editor"), json_editor_settings);
+        var myJsonTextArea = document.getElementById("code_editor_json");
+        editorJSON = CodeMirror(function (elt) {
+            myJsonTextArea.parentNode.replaceChild(elt, myJsonTextArea);
+        }, json_editor_settings);
+
+
+        editorJSON.setValue(JSON.stringify({{descriptor_strings.descriptor_string_json | safe}}, null, "\t"));
+        editorJSON.setOption("autoRefresh", true);
+        var myYamlTextArea = document.getElementById("code_editor_yaml");
+        editorYaml = CodeMirror(function (elt) {
+            myYamlTextArea.parentNode.replaceChild(elt, myYamlTextArea);
+        }, yaml_editor_settings);
+        var des_strings = {{descriptor_strings | safe}};
+        editorYaml.setValue(des_strings.descriptor_string_yaml);
+
+    });
+
+
+    function update(e) {
+        var dialog = bootbox.dialog({
+            message: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i> Loading...</div>',
+            closeButton: false
+        });
+
+        var id = $('.nav-tabs .active').attr('id');
+        var type, text;
+        switch (id) {
+            case 'yaml_li':
+                type = 'yaml';
+                text = editorYaml.getValue();
+                break;
+            case 'json_li':
+                type = 'json';
+                text = editorJSON.getValue();
+                break;
+        }
+        $.ajax({
+            url: "/projects/{{project_id}}/descriptors/{{descriptor_type}}/{{descriptor_id}}/",
+            type: 'POST',
+            dataType: 'json',
+            data: {
+                'csrfmiddlewaretoken': '{{csrf_token}}',
+                'type': type,
+                'text': text
+            },
+            success: function (result) {
+                dialog.modal('hide');
+
+            },
+            error: function (result) {
+                console.log(result);
+                dialog.modal('hide');
+                bootbox.alert(result);
+            }
+        });
+    }
+
+</script>
+{% endblock %}
+
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
diff --git a/projecthandler/template/project/osm/descriptor/descriptorlist.html b/projecthandler/template/project/osm/descriptor/descriptorlist.html
new file mode 100644 (file)
index 0000000..56ee44d
--- /dev/null
@@ -0,0 +1,57 @@
+{% load staticfiles %}
+{% load get %}
+<div class="col-md-12">
+
+    <div class="box">
+        <div class="box-header with-border">
+            <h3 class="box-title">
+            </h3>
+            <div class="box-tools">
+
+                <a href="{% url 'projects:new_descriptor' project_id=project_id descriptor_type=descriptor_type %}"
+                   class="btn btn-block btn-primary btn-sm"><i
+                        class="fa fa-upload"></i><span> Onboard {{descriptor_type|upper}}</span></a>
+            </div>
+        </div>
+        <div class="box-body">
+            <table id="descriptors_table" class="table table-bordered table-striped dataTable"
+                       role="grid">
+                <thead>
+                <tr role="row">
+                    <th>Id</th>
+                    <th>Name</th>
+                    <th>Description</th>
+                    <th style="width:10%">Vendor</th>
+                    <th style="width:5%">Version</th>
+                    <th style="width:35%">Actions</th>
+                </tr>
+                </thead>
+                <tbody>
+                {% for k in descriptors %}
+                <tr role="row">
+                    <td>{{ k|get:"_id" }}</td>
+                    <td>{{ k|get:"short-name" }} </td>
+                    <td>{{ k|get:"description" }} </td>
+                    <td>{{ k|get:"vendor" }} </td>
+                    <td>{{ k|get:"version" }} </td>
+                    <td>
+                        <div class="btn-group">
+                            {% if descriptor_type == "nsd" %}
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Instantiate NS" onclick="javascript:openModalCreateNS('{{project_id}}', '{{descriptor_type}}', '{{ k|get:"_id" }}')"><i class="fa fa-paper-plane"></i></button>
+                            {% endif %}
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Edit" onclick="javascript:openDescriptorView('{{project_id}}', '{{descriptor_type}}', '{{ k|get:"_id" }}')"><i class="fa fa-edit"></i></button>
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Show content" onclick="javascript:openPackageContentList('{{project_id}}', '{{descriptor_type}}', '{{ k|get:"_id" }}')"><i class="fa fa-folder-open"></i></button>
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Show Graph" onclick="location.href='/projects/{{project_id}}/graph?type={{descriptor_type}}&id={{ k|get:"_id" }}'" disabled><i class="fa fa-sitemap fa-fw"></i></button>
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Download package" onclick="location.href='{% url 'projects:custom_action' project_id=project_id descriptor_type=descriptor_type descriptor_id=k|get:'_id' action_name='download_pkg' %}'"><i class="fa fa-download fa-fw"></i></button>
+                            <button type="button" class="btn btn-default" data-container="body" data-toggle="tooltip" data-placement="top" title="Delete" onclick="javascript:deletePackage('{{project_id}}', '{{descriptor_type}}', '{{ k|get:"_id" }}')"><i class="fa fa-trash-o"></i></button>
+                        </div>
+
+                    </td>
+                </tr>
+                {% endfor %}
+                </tbody>
+
+            </table>
+        </div>
+    </div>
+</div>
diff --git a/projecthandler/template/project/osm/descriptor/modal/choose_node_id.html b/projecthandler/template/project/osm/descriptor/modal/choose_node_id.html
new file mode 100644 (file)
index 0000000..91c4d20
--- /dev/null
@@ -0,0 +1 @@
+{% extends "descriptor/modal/choose_node_id.html" %}
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/descriptor/modal/create_link_chooser.html b/projecthandler/template/project/osm/descriptor/modal/create_link_chooser.html
new file mode 100644 (file)
index 0000000..ed9a4a5
--- /dev/null
@@ -0,0 +1,21 @@
+<div class="modal" id="modal_create_link_chooser">
+  <div class="modal-dialog">
+    <div class="modal-content">
+      <div class="modal-header">
+        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+          <span aria-hidden="true">×</span></button>
+        <h4 class="modal-title" id="modal_chooser_title">Default Modal</h4>
+      </div>
+      <div class="modal-body" >
+          <select class="form-control" id="selection_chooser">
+          </select>
+    </div>
+      <div class="modal-footer">
+        <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
+        <button type="button" class="btn btn-primary" id="save_chooser">Save changes</button>
+      </div>
+    </div>
+    <!-- /.modal-content -->
+  </div>
+  <!-- /.modal-dialog -->
+</div>
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/modal/files_list.html b/projecthandler/template/project/osm/modal/files_list.html
new file mode 100644 (file)
index 0000000..13105fe
--- /dev/null
@@ -0,0 +1,33 @@
+<div class="modal" id="modal_files_list" xmlns="http://www.w3.org/1999/html">
+    <div   class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 id="files_list_tbody_title" class="modal-title"></h4>
+            </div>
+
+            <div class="modal-body" id="modal_files_list_body" >
+                <table id="files_list" class="table table-condensed">
+        <tbody id="files_list_tbody">
+            <tr>
+                <th style="width: 10px">#</th>
+                <th>File Name</th>
+                <th>Actions</th>
+            </tr>
+            <tr>
+                <td>-</td>
+                <td>name</td>
+                <td>-</td>
+            </tr>
+        </tbody>
+    </table>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Close</button>
+            </div>
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
diff --git a/projecthandler/template/project/osm/modal/modal_keyboard_info.html b/projecthandler/template/project/osm/modal/modal_keyboard_info.html
new file mode 100644 (file)
index 0000000..bf39e69
--- /dev/null
@@ -0,0 +1 @@
+{% extends "modal/modal_keyboard_info_base.html" %}
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/osm_new_project.html b/projecthandler/template/project/osm/osm_new_project.html
new file mode 100644 (file)
index 0000000..cfdec29
--- /dev/null
@@ -0,0 +1,25 @@
+<div id="div-file-upload-osm" class="start-selector"  style="display: none;">
+    <h4>Select Descriptors File from your computer</h4>
+
+    <div class="tab-pane col-md-6" >
+
+        <!-- Standar Form -->
+        <h3>Descriptor</h3>
+        <div class="form-inline">
+            <div class="form-group">
+                <input type="file"  name="ns_files" id="ns-js-upload-files" multiple>
+            </div>
+        </div>
+
+    </div>
+
+</div>
+
+<div id="div-example-osm" class="start-selector" style="display: none;">
+
+    <select id="example-osm" class="example-selector" data-type="osm" name="example-osm-id">
+
+    </select>
+
+
+</div>
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/osm_project_delete.html b/projecthandler/template/project/osm/osm_project_delete.html
new file mode 100644 (file)
index 0000000..50bf685
--- /dev/null
@@ -0,0 +1,36 @@
+{% extends "project_delete.html" %}
+
+{% block head_base %}
+    {% with skin_css="AdminLTE/dist/css/skins/skin-purple.min.css"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block body %}
+    {% with skin="purple"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+{% block logo_sidebar %}
+    {% with logo_mini="assets/img/osm_small_logo.png" logo="assets/img/OSM-logo.png"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block head_block %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html" %}
+{% endblock %}
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/osm_project_descriptors.html b/projecthandler/template/project/osm/osm_project_descriptors.html
new file mode 100644 (file)
index 0000000..30ea76a
--- /dev/null
@@ -0,0 +1,121 @@
+{% extends "project_descriptors.html" %}
+{% load staticfiles %}
+
+
+{% block head_block %}
+       {{ block.super }}
+<link rel="stylesheet" href="/static/bower_components/select2/dist/css/select2.min.css">
+{% endblock %}
+
+{% block title_header_big %}
+    {{ block.super }}
+  {% if descriptor_type  %}
+     {{ descriptor_type|upper }}
+  {% else%}
+    Undefined
+  {% endif %} Packages
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    <li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview_data.name}}</a></li>
+    <li><a> {% if descriptor_type  %}
+     {{ descriptor_type }}
+  {% else%}
+    Undefined
+  {% endif %} Packages</a></li>
+
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+{% include 'osm/modal/files_list.html' %}
+{% include 'modal/instance_create.html' %}
+<div class="row">
+    {% include "osm/descriptor/descriptorlist.html" %}
+</div>
+{% endblock %}
+
+
+
+
+{% block resource_block %}
+       {{ block.super }}
+    <script src="/static/bower_components/select2/dist/js/select2.js"></script>
+    <script>
+        function deletePackage(project_id, descriptor_type, package_id) {
+            bootbox.confirm("Are you sure want to delete?", function (result) {
+                if (result) {
+                    location.href = '/projects/' + project_id + '/descriptors/' + descriptor_type + '/' + package_id + '/delete'
+                }
+            })
+        }
+
+        function openModalCreateNS(project_id, descriptor_type, descriptor_id) {
+            select2_groups = $('#vimAccountId').select2({
+                placeholder: 'Select VIM',
+                ajax: {
+                    url: '{% url "vim:list" %}',
+                    dataType: 'json',
+                    processResults: function (data) {
+                        vims = [];
+                        if (data['datacenters']){
+                            for(d in data['datacenters']){
+                            var datacenter = data['datacenters'][d];
+                            vims.push({id: datacenter['_id'], text: datacenter['name']})
+                        }
+                        }
+
+                        return {
+                            results: vims
+                        };
+                    }
+                }
+            });
+            $('#nsdId').val(descriptor_id);
+            $('#modal_new_instance').modal('show');
+        }
+
+        function openPackageContentList(project_id, type, pkg_id) {
+            var dialog = bootbox.dialog({
+                message: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i> Loading...</div>',
+                closeButton: true
+            });
+            $.ajax({
+                url: '/projects/' + project_id + '/descriptors/' + type + '/' + pkg_id + '/action/get_package_files_list',
+                type: 'GET',
+                dataType: "json",
+                contentType: "application/json;charset=utf-8",
+                success: function (result) {
+                    //$('#modal_show_vim_body').empty();
+                    console.log(result)
+                    dialog.modal('hide');
+                    build_file_list("Files in " + pkg_id, result.files);
+                },
+                error: function (result) {
+                    dialog.modal('hide');
+                    bootbox.alert("An error occurred while retrieving the package content.");
+                }
+            });
+        }
+
+
+        function build_file_list(title, list) {
+            $('#files_list_tbody').find('tr:gt(0)').remove();
+            $('#files_list_tbody_title').text(title)
+            for (var i in list) {
+                var template = '<tr><td>-</td><td>' + list[i] + '</td><td><button type="button" class="btn btn-default" onclick="" disabled><i class="fa fa-folder-open"></i></button></td></tr>'
+                $('#files_list_tbody').append(template)
+            }
+            $('#modal_files_list').modal('show');
+        }
+    </script>
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
diff --git a/projecthandler/template/project/osm/osm_project_details.html b/projecthandler/template/project/osm/osm_project_details.html
new file mode 100644 (file)
index 0000000..ab90385
--- /dev/null
@@ -0,0 +1,79 @@
+{% extends "project_details.html" %}
+{% block head_base %}
+    {{ block.super }}
+{% endblock %}
+
+{% block body %}
+        {{ block.super }}
+{% endblock %}
+{% block logo_sidebar %}
+        {{ block.super }}
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block content_body %}
+
+<div class="row">
+    <div class="col-md-6">
+        <div class="box box-widget">
+            <div class="box-footer no-padding">
+                <ul class="nav nav-stacked">
+                    <li><a><b>Project Name:</b> <span
+                        class="pull-right">{{project_overview.name}}</span></a>
+                    </li>
+                    <li><a><b>Info:</b> <span
+                            class="pull-right">{{project_overview.info}}</span></a></li>
+                    <li><a><b>Last updated:</b> <span
+                        class="pull-right">{{project_overview.updated_date}}</span></a>
+                    </li>
+                    <li><a><b>Owner:</b> <span
+                            class="pull-right">{{project_overview.owner}}</span></a></li>
+                </ul>
+
+            </div>
+
+        </div>
+    </div>
+    <div class="col-md-6">
+        <div class="row">
+            <div class="col-md-6 ">
+                <div class="small-box bg-aqua">
+                    <div class="inner"><h3>{{ project_overview.nsd }}</h3>
+                        <p>NS packages</p></div>
+                    <div class="icon"><i class="fa fa-archive"></i></div>
+                    <a href="/projects/{{ project_id }}/descriptors/nsd" class="small-box-footer">Open list <i
+                            class="fa fa-arrow-circle-right"></i></a></div>
+            </div>
+            <div class="col-md-6 ">
+                <div class="small-box bg-aqua">
+                    <div class="inner"><h3>{{ project_overview.vnfd }}</h3>
+                        <p>VNF packages</p></div>
+                    <div class="icon"><i class="fa fa-archive"></i></div>
+                    <a href="/projects/{{ project_id }}/descriptors/vnfd" class="small-box-footer">Open list <i
+                            class="fa fa-arrow-circle-right"></i></a></div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="col-md-6 ">
+                <div class="small-box bg-maroon">
+                    <div class="inner"><h3>{{ project_overview.ns }}</h3>
+                        <p>NS Instances</p></div>
+                    <div class="icon"><i class="fa fa-list"></i></div>
+                    <a href='{% url "projects:instances:list" project_id=project_id type="ns"  %}' class="small-box-footer">Open list <i
+                            class="fa fa-arrow-circle-right"></i></a></div>
+            </div>
+
+        </div>
+
+    </div>
+</div>
+
+{% endblock %}
+
+
+{% block footer %}
+    {% include "footer.html" %}
+{% endblock %}
diff --git a/projecthandler/template/project/osm/osm_project_left_sidebar.html b/projecthandler/template/project/osm/osm_project_left_sidebar.html
new file mode 100644 (file)
index 0000000..029ae11
--- /dev/null
@@ -0,0 +1,56 @@
+<aside class="main-sidebar">
+    <!-- sidebar: style can be found in sidebar.less -->
+    <section class="sidebar">
+      <!-- sidebar menu: : style can be found in sidebar.less -->
+      <ul class="sidebar-menu" data-widget="tree">
+        <li class="header">MAIN NAVIGATION</li>
+        {% url "home" as  home_url %}
+          <li {% if request.get_full_path == home_url %} class="active" {% endif %} ><a href='{% url "home" %}'><i class="fa fa-home fa-fw"></i><span>Home</span></a></li>
+          <li class="header">PROJECT</li>
+          {% url "projects:open_project"  project_id=project_id as  project_url %}
+          <li {% if request.get_full_path == project_url %} class="active" {% endif %} >
+              <a href='{% url "projects:open_project"  project_id=project_id%}'>
+                  <i class="fa fa-file-text fa-fw"></i><span>Overview</span>
+              </a>
+          </li>
+          {% url "projects:list_descriptors"  project_id=project_id descriptor_type="vnfd" as  nsd_list_url %}
+          {% url "projects:list_descriptors"  project_id=project_id descriptor_type='vnfd' as  vnfd_list_url %}
+        <li {% if request.get_full_path == nsd_list_url %} class="active treeview" {% else %} class="treeview" {% endif %} >
+                <a href="#">
+                    <i class="fa fa-archive fa-fw"></i> <span>Packages {{nsd_list}}</span>
+                    <span class="pull-right-container">
+                        <i class="fa fa-angle-left pull-right"></i>
+                    </span>
+                </a>
+                <ul class="treeview-menu">
+                    <li {% if request.get_full_path == nsd_list_url %} class="active" {% endif %}>
+                        <a href="/projects/{{project_id}}/descriptors/nsd">
+                            <i class="fa fa-archive fa-fw"></i>NS Packages
+                        </a>
+                    </li>
+                    <li {% if request.get_full_path == vnfd_list_url %} class="active" {% endif %}>
+                        <a href="/projects/{{project_id}}/descriptors/vnfd">
+                            <i class="fa fa-archive fa-fw"></i>VNF Packages
+                        </a>
+                    </li>
+
+                </ul>
+            </li>
+            {% url "projects:instances:list" project_id=project_id type='ns' as  instance_ns_list_url %}
+            <li {% if request.get_full_path == instance_ns_list_url %} class="active" {% endif %} >
+                <a href='{% url "projects:instances:list" project_id=project_id  type="ns"%}'>
+                    <i class="fa fa-list fa-fw"></i><span>NS Instances</span>
+                </a>
+            </li>
+            <li class="header">CONFIG</li>
+            {% url "vim:list"  type='ns' as  vim_list_url %}
+             <li {% if request.get_full_path == vim_list_url %} class="active" {% endif %}>
+                 <a href='{% url "vim:list" %}'>
+                     <i class="fa fa-list fa-fw"></i><span>VIM Accounts</span>
+                 </a>
+             </li>
+
+      </ul>
+    </section>
+    <!-- /.sidebar -->
+  </aside>
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/project_graph.html b/projecthandler/template/project/osm/project_graph.html
new file mode 100644 (file)
index 0000000..15d2eeb
--- /dev/null
@@ -0,0 +1,61 @@
+{% extends "project_graph_base.html" %}
+
+{% load staticfiles %}
+{% block head_base %}
+    {% with skin_css="AdminLTE/dist/css/skins/skin-purple.min.css"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block body %}
+    {% with skin="purple"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+{% block logo_sidebar %}
+    {% with logo_mini="assets/img/osm_small_logo.png" logo="assets/img/OSM-logo.png"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'osm/osm_project_left_sidebar.html' %}
+{% endblock %}
+
+{% block topology_toolbar %}
+    {{ block.super }}
+        {% include 'osm/topology_toolbar.html' %}
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+ {% csrf_token %}
+
+
+{% include 'osm/descriptor/modal/create_link_chooser.html' %}
+{% include 'osm/descriptor/modal/choose_node_id.html' %}
+{% include 'osm/modal/modal_keyboard_info.html' %}
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+    <script src="{% static "topology3D/js/model_graph_editor.js" %}"></script>
+    <!-- osm -->
+    <script src="{% static "src/projecthandler/osm/gui_properties.js" %}"></script>
+    <script src="{% static "src/projecthandler/osm/project_graph.js" %}"></script>
+    <script src="{% static "src/projecthandler/osm/controller.js" %}"></script>
+
+<script>
+    graph_editor.project_id = '{{project_id}}'
+
+</script>
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
\ No newline at end of file
diff --git a/projecthandler/template/project/osm/topology_toolbar.html b/projecthandler/template/project/osm/topology_toolbar.html
new file mode 100644 (file)
index 0000000..9a1f528
--- /dev/null
@@ -0,0 +1,6 @@
+{% extends 'topology_toolbar.html' %}
+
+{% block topology_toolbar_buttons %}
+    {{ block.super }}
+
+{% endblock %}
diff --git a/projecthandler/template/project/project_delete.html b/projecthandler/template/project/project_delete.html
new file mode 100644 (file)
index 0000000..e6afc5e
--- /dev/null
@@ -0,0 +1,73 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block left_sidebar %}
+
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    {% if project_id  %}
+    <li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview_data.name}}</a></li>
+    <li>Delete Project</li>
+    {% endif %}
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+    <div class="row">
+    {% if project_id  %}
+        <div class="col-sm-5">
+      <div class="box box-danger">
+        <div class="box-header with-border">
+              <i class="fa fa-warning"></i>
+
+              <h3 class="box-title">Warning</h3>
+        </div>
+          <div class="box-body">
+              <form role="form" method="post">
+                  {% csrf_token %}
+                  <div class="box-body">
+                        <p>The Project <strong>{{project_name}}</strong> will be deleted!</p>
+                      <!-- /.box-body -->
+                  </div>
+                   <div class="box-footer">
+                          <button type="submit" class="btn btn-danger pull-right"><i class="fa fa-trash"></i> Delete</button>
+                   </div>
+              </form>
+
+        </div><!-- /.box-body -->
+      </div><!-- /.box -->
+    </div>
+    {% endif %}
+</div>
+
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+
+    <script>
+
+
+
+
+$(document).ready(function () {
+    {% if project_id == None %}
+      window.location.href='{% url "home"  %}';
+    {% endif %}
+
+});
+
+
+
+    </script>
+{% endblock %}
+
diff --git a/projecthandler/template/project/project_descriptors.html b/projecthandler/template/project/project_descriptors.html
new file mode 100644 (file)
index 0000000..6f7345e
--- /dev/null
@@ -0,0 +1,54 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+    <link rel="stylesheet" href="/static/bower_components/datatables.net-bs/css/dataTables.bootstrap.min.css">
+{% endblock %}
+{% block title_header_big %}
+
+{% endblock %}
+
+{% block left_sidebar %}
+
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    <li><a href="{% url 'projects:projects_list' %}">Projects</a></li>
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+{% if alert_message %}
+    {% if alert_message.success %}
+        <div class="alert alert-success alert-dismissible fade in">
+            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+            <h4><i class="icon fa fa-check"></i> Success</h4>
+            {{alert_message.message}}
+        </div>
+    {% endif %}
+    {% if not alert_message.success %}
+        <div class="alert alert-danger alert-dismissible fade in">
+            <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
+            <h4><i class="icon fa fa-ban"></i> Alert!</h4>
+            {{alert_message.message}}
+        </div>
+    {% endif %}
+{% endif %}
+
+{% endblock %}
+
+
+
+
+{% block resource_block %}
+       {{ block.super }}
+
+    <script src="/static/bower_components/datatables.net/js/jquery.dataTables.min.js"></script>
+    <script src="/static/bower_components/datatables.net-bs/js/dataTables.bootstrap.min.js"></script>
+    <script src="/static/src/projecthandler/descriptorslist.js"></script>
+
+{% endblock %}
+
diff --git a/projecthandler/template/project/project_details.html b/projecthandler/template/project/project_details.html
new file mode 100644 (file)
index 0000000..e944e0d
--- /dev/null
@@ -0,0 +1,78 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block left_sidebar %}
+    {{ block.super }}
+
+{% endblock %}
+
+{% block title_header_big %}
+    {{ block.super }}
+    {{project_overview.name}}
+{% endblock %}
+
+{% block title_header_small %}
+    {{ block.super }}
+    Overview
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    <li><a href="{% url 'projects:projects_list' %}">Projects</a></li>
+    <li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview.name}}</a></li>
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+<div class="row">
+    <div class="col-md-6">
+        <div class="box box-widget">
+            <div class="box-footer no-padding">
+                <ul class="nav nav-stacked">
+                    <li><a><b>Project Name:</b> <span class="pull-right">{{project_overview.name}}</span></a>
+                    </li>
+                    <li><a><b>Info:</b> <span
+                            class="pull-right">{{project_overview.info}}</span></a></li>
+                    <li><a><b>Last updated:</b> <span class="pull-right">{{project_overview.updated_date}}</span></a>
+                    </li>
+                    <li><a><b>Owner:</b> <span
+                            class="pull-right">{{project_overview.owner}}</span></a></li>
+
+                    <li><a><b>Validated:</b>
+                        {% if project_overview.validated %}
+                        <span class="pull-right badge bg-green">{{project_overview.validated}}</span>
+                        {% else %}
+                        <span class="pull-right badge bg-red">{{project_overview.validated}}</span>
+                        {% endif %}
+                    </a>
+
+                    </li>
+
+                </ul>
+
+            </div>
+
+        </div>
+    </div>
+    <div class="col-md-6">
+        {% block content_descritors_col %}
+
+        {% endblock %}
+    </div>
+</div>
+
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+
+{% endblock %}
+
+
diff --git a/projecthandler/template/project/project_graph_base.html b/projecthandler/template/project/project_graph_base.html
new file mode 100644 (file)
index 0000000..7d344da
--- /dev/null
@@ -0,0 +1,58 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+       <link rel="stylesheet" href="{% static "topology3D/css/graph_editor_d3js.css" %}">
+       <link rel="stylesheet" href="{% static "topology3D/css/d3-context-menu.css" %}">
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    <li><a href="{% url 'projects:open_project' project_id=project_id %}">{{project_overview_data.name}}</a></li>
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+ {% csrf_token %}
+<div class="row" >
+    <div class="col-md-12">
+        {% block topology_toolbar %}
+
+
+        {% endblock %}
+        <div id="graph_ed_container" style="width: 100%; height:100%; background-color: white; border: 2px #3c8dbc solid;">
+
+        </div>
+    </div>
+</div>
+
+
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+    <!-- d3.js -->
+    <script src="{% static "bower_components/d3/d3.min.js" %}" charset="utf-8"></script>
+
+    <!-- topology3D -->
+    <script src="{% static "topology3D/js/d3-context-menu.js" %}"></script>
+    <script src="{% static "topology3D/js/event.js" %}"></script>
+    <script src="{% static "topology3D/js/graph_editor.js" %}"></script>
+    <script src="{% static "topology3D/js/graph_request.js" %}"></script>
+
+{% endblock %}
+
+
+{% block header %}
+{% endblock %}
+
diff --git a/projecthandler/template/project/projectlist.html b/projecthandler/template/project/projectlist.html
new file mode 100644 (file)
index 0000000..5fd3b9d
--- /dev/null
@@ -0,0 +1,84 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+
+{% include 'left_sidebar_base.html' %}
+
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+    <li><a href="{% url 'projects:projects_list' %}">Projects</a></li>
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+ {% csrf_token %}
+<div class="row">
+<div class="col-md-12">
+
+    <div class="box">
+        <div class="box-header with-border">
+                  <h3 class="box-title">Projects</h3>
+            <div class="box-tools">
+                <a href="" class="btn btn-block btn-primary btn-sm" disabled><i class="fa fa-plus"></i><span> New Project</span></a>
+            </div>
+        </div>
+        <div class="box-body">
+                       <table id="projects_table" class="table table-bordered table-striped">
+                               <thead>
+                                       <tr>
+
+                                               <th>Name</th>
+                                               <th>Info</th>
+                                               <th>Type</th>
+                                               <th>Modification Date</th>
+                                               <th>Creation Date</th>
+
+                                               <th>Owner</th>
+
+                                               <th><i class="fa fa-trash-o"></i></th>
+                                       </tr>
+                               </thead>
+                               <tbody>
+                               {% for p in projects %}
+                                       <tr>
+
+                                               <td>
+                                                       <a href="javascript:openProject('{{ p.id }}')" >{{ p.name }}</a>
+                                               </td>
+                                               <td>{{ p.info }}</td>
+                                               <td>{{ p.get_type }}</td>
+                                               <td>{{ p.updated_date }}</td>
+                                               <td>{{ p.created_date }}</td>
+                                               <td>{{ p.owner.username }}</td>
+
+                                               <td><a href="#"> Delete</a></td>
+
+                                       </tr>
+                               {% endfor %}
+                               </tbody>
+                       </table>
+               </div>
+    </div>
+</div>
+
+</div>
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/projecthandler/template/project/repository/repo_base_left_sidebar.html b/projecthandler/template/project/repository/repo_base_left_sidebar.html
new file mode 100644 (file)
index 0000000..3367eeb
--- /dev/null
@@ -0,0 +1,12 @@
+{% extends "left_sidebar_base.html" %}
+
+{% block left_sidebar_base %}
+    <li class="header">REPOSITORY</li>
+    {% url "repos:repos_list" as  repo_list_url %}
+    <li {% if request.get_full_path == repo_list_url %} class="active" {% endif %}><a href='{% url "repos:repos_list" %}'><i class="fa fa-git fa-fw"></i><span>Ropository Registered</span></a></li>
+
+    {% block li_list %}
+
+
+    {% endblock %}
+{% endblock %}
diff --git a/projecthandler/template/project/repository/repo_create_modal.html b/projecthandler/template/project/repository/repo_create_modal.html
new file mode 100644 (file)
index 0000000..6df53b5
--- /dev/null
@@ -0,0 +1,43 @@
+<div class="modal" id="modal_new_repository" xmlns="http://www.w3.org/1999/html">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" id="modal_new_repo_title">New Repository</h4>
+            </div>
+            <div id="div_chose_id" class="modal-body">
+                <!-- form start -->
+            <form action='{% url "repos:new_repo" %}' class="form-horizontal" role="form" method="post" enctype="multipart/form-data">
+                {% csrf_token %}
+                    <div class="box-body">
+                        <div class="form-group">
+                            <label for="name" class="col-sm-2 control-label">Name</label>
+
+                            <div class="col-sm-10">
+                                <input type="text" class="form-control" id="name" placeholder="Name" name="name" required>
+                            </div>
+                        </div>
+                        <div class="form-group">
+                            <label for="base_url_repo" class="col-sm-2 control-label">Base Url</label>
+
+                                <div class="col-sm-10">
+                                    <input class="form-control required" id="base_url_repo"
+                                           name="base_url_repo" placeholder="Base Url" pattern="((git|ssh|http(s)?)|(git@[\w\.]+))(:(\/\/)?)([\w\.\/\-]*)">
+                                </div>
+                        </div>
+                    </div>
+                    <!-- /.box-body -->
+                    <div class="box-footer">
+                        <button type="button" class="btn btn-default pull-left" data-dismiss="modal">Cancel</button>
+                        <button type="submit" class="btn btn-info pull-right" id="save_new_repo">Register</button>
+                    </div>
+                    <!-- /.box-footer -->
+                </form>
+            </div>
+
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
\ No newline at end of file
diff --git a/projecthandler/template/project/repository/repo_list.html b/projecthandler/template/project/repository/repo_list.html
new file mode 100644 (file)
index 0000000..1f0a8f3
--- /dev/null
@@ -0,0 +1,120 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+{{ block.super }}
+
+<style type="text/css">
+
+  input:required:invalid, input:focus:invalid {
+
+    background-position: right top;
+    background-repeat: no-repeat;
+  }
+  input:required:valid {
+
+    background-position: right top;
+    background-repeat: no-repeat;
+  }
+
+.form-control.select2-hidden-accessible {
+    top: 30px;
+}
+
+
+</style>
+{% endblock %}
+
+{% block title_header_big %}
+{{ block.super }}
+{% endblock %}
+
+{% block left_sidebar %}
+<!-- Left side column. contains the sidebar -->
+{% include "repository/repo_base_left_sidebar.html" %}
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+{{ block.super }}
+
+{% endblock %}
+
+{% block content_body %}
+{{ block.super }}
+{% csrf_token %}
+    <div class="row">
+
+        <div class="col-md-12">
+
+            <div class="box">
+                <div class="box-header with-border">
+                    <h3 class="box-title">Registered Repository</h3>
+                    <div class="box-tools">
+                        <a href="#" class="btn btn-block btn-primary btn-sm" data-toggle="modal"
+                           data-target="#modal_new_repository"><i
+                                class="fa fa-plus"></i><span> New Repository</span></a>
+                    </div>
+                </div>
+                <div class="box-body">
+                    <table id="deployments_table" class="table table-bordered table-striped">
+                        <thead>
+                        <tr>
+
+                            <th>Name</th>
+                            <th>Url</th>
+                            <th>Last Update</th>
+                            <th>Actions</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {% for repo in repos %}
+                            <tr>
+
+                                <td>
+                                    {{ repo.name }}
+                                </td>
+                                <td>
+                                    {{ repo.base_url }}
+                                </td>
+                                <td>{{ repo.last_update }}</td>
+                                <td>
+                                    <div class="btn-group">
+                                        <button type="button" class="btn btn-default"><i class="fa fa-cog"
+                                                                                         aria-hidden="true"></i>
+                                        </button>
+                                        <button type="button" class="btn btn-default dropdown-toggle"
+                                                data-toggle="dropdown"
+                                                aria-expanded="false">
+                                            <span class="caret"></span>
+                                            <span class="sr-only">Toggle Dropdown</span>
+                                        </button>
+                                        <ul class="dropdown-menu" role="menu">
+                                            <li class='disabled'><a href='#'><i class="fa fa-edit fa-fw"></i>Edit</a>
+                                            </li>
+                                            <li><a href='{% url "repos:delete_repo" repo_id=repo.id %}'>
+                                                <i class="fa fa-trash fa-fw"></i>Delete</a></li>
+                                        </ul>
+                                    </div>
+                                </td>
+
+                            </tr>
+                        {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+    </div>
+{% include 'repository/repo_create_modal.html' %}
+
+{% endblock %}
+
+{% block resource_block %}
+{{ block.super }}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/projecthandler/tests.py b/projecthandler/tests.py
new file mode 100644 (file)
index 0000000..d334413
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.test import TestCase
+
+# Create your tests here.
diff --git a/projecthandler/urls/__init__.py b/projecthandler/urls/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/projecthandler/urls/project.py b/projecthandler/urls/project.py
new file mode 100644 (file)
index 0000000..61e4d4e
--- /dev/null
@@ -0,0 +1,54 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.conf.urls import url, include
+from projecthandler import views
+
+urlpatterns = [
+    url(r'^list/', views.user_projects, name='projects_list'),
+    url(r'^new/', views.create_new_project, name='new_project'),
+    url(r'^(?P<project_id>\d+)/$', views.open_project, name='open_project'),
+    url(r'^(?P<project_id>\d+)/instances/', include('instancehandler.urls', namespace='instances'), name='instances_base'),
+    url(r'^(?P<project_id>\d+)/delete$', views.delete_project, name='delete_project'),
+    url(r'^(?P<project_id>\d+)/graph(/$)', views.graph, name='graph_view'),
+    url(r'^(?P<project_id>\d+)/graph/graph_data(/$)', views.graph_data, name='graph_data'),
+    url(r'^(?P<project_id>\d+)/graph/graph_data/(?P<descriptor_id>[-\w]+)(/$)', views.graph_data, name='graph_data'),
+    url(r'^(?P<project_id>\d+)/graph/positions$', views.graph_positions, name='graph_positions'),
+    url(r'^(?P<project_id>\d+)/graph/unusedvnf/(?P<nsd_id>\w+)(/$)', views.unused_vnf, name='unused_vnf'),
+    url(r'^(?P<project_id>\d+)/graph/addelement$', views.add_element, name='addelement'),
+    url(r'^(?P<project_id>\d+)/graph/overviewelement$', views.overviewelement, name='overviewelement'),
+    url(r'^(?P<project_id>\d+)/graph/addnodetovnffg', views.add_node_to_vnffg, name='addnodetovnffg'),
+    url(r'^(?P<project_id>\d+)/graph/removeelement$', views.remove_element, name='removeelement'),
+    url(r'^(?P<project_id>\d+)/graph/addlink$', views.add_link, name='addlink'),
+    url(r'^(?P<project_id>\d+)/graph/removelink$', views.remove_link, name='removelink'),
+    url(r'^(?P<project_id>\d+)/graph/availablenodes', views.get_available_nodes, name='get_available_nodes'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)(/$)', views.show_descriptors, name='list_descriptors'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)/(?P<descriptor_id>[-\w]+)(/$)',
+        views.edit_descriptor, name='edit_descriptor'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)/(?P<descriptor_id>[-\w]+)/delete$',
+        views.delete_descriptor,
+        name='delete_descriptor'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)/(?P<descriptor_id>[-\w]+)/clone$',
+        views.clone_descriptor,
+        name='clone_descriptor'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)/(?P<descriptor_id>[-\w]+)/action/(?P<action_name>[-\w]+)',
+        views.custom_action,
+        name='custom_action'),
+    url(r'^(?P<project_id>\d+)/descriptors/(?P<descriptor_type>\w+)/new$', views.new_descriptor,
+        name='new_descriptor'),
+
+
+]
\ No newline at end of file
diff --git a/projecthandler/views.py b/projecthandler/views.py
new file mode 100644 (file)
index 0000000..8c553a4
--- /dev/null
@@ -0,0 +1,537 @@
+#
+#   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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.
+#
+
+import json
+
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, JsonResponse
+from django.middleware.csrf import get_token
+from django.shortcuts import render, redirect
+from django.template.loader import render_to_string
+from lib.util import Util
+from sf_user.models import CustomUser
+import tarfile
+
+
+from projecthandler.osm_model import OsmProject
+from projecthandler.models import Project
+
+
+Project.add_project_type('osm', OsmProject)
+
+
+
+from projecthandler.models import Repository
+
+@login_required
+def home(request):
+    return render(request, 'home.html', {})
+
+
+@login_required
+def create_new_project(request):
+    if request.method == 'POST':
+        error_msgs = []
+        user = CustomUser.objects.get(id=request.user.id)
+        name = request.POST.get('name', 'WithoutName')
+        info = request.POST.get('info', ' ')
+        type = request.POST.get('type', '')
+        start_from = request.POST.get('startfrom', 'scratch')
+
+        project_types = Project.get_project_types()
+        if type in project_types:
+            project_class = project_types[type]
+
+        else:
+            # FIXME this error is not handled
+            error_msgs.append('Project type undefined.')
+            return render(request, 'error.html',
+                          {'error_msg': 'Error creating new project, project type undefined. Please retry.'})
+
+        try:
+
+            if start_from == 'scratch':
+                print 'from scratch'
+                data_project = {}
+
+            elif start_from == 'files':
+                print 'from files'
+                data_project = project_class.data_project_from_files(request)
+
+            elif start_from == 'example':
+                print 'from example'
+                data_project = project_class.data_project_from_example(request)
+
+            project = project_class.create_project(name, user, False, info, data_project)
+            # print project.get_dataproject()
+
+
+        except Exception as e:
+            print 'Error creating ' + type + ' project! Please retry.'
+            print e
+            return render(request, 'error.html', {'error_msg': 'Error creating ' + type + ' project! Please retry.'})
+        return redirect('projects:open_project', project_id=project.id)
+
+    elif request.method == 'GET':
+        csrf_token_value = get_token(request)
+        result = {}
+        data_type_selector = [{
+            'id': '-1',
+            'text': 'Select an option'
+        }]
+        type_example_files = {}
+        type_container_template = ''
+        project_types = Project.get_project_types()
+        print "project_types", project_types.keys()
+        for type in project_types:
+            project_class = project_types[type]
+            type_example_files.update(project_class.get_example_list())
+            data_type_selector.append({
+                'id': type,
+                'text': type,
+                'value': type
+            })
+            type_container_template += render_to_string(type + '/' + type + '_new_project.html')
+
+        result.update({'type_example_files': json.dumps(type_example_files)})
+        result.update({'data_type_selector': json.dumps(data_type_selector)})
+        result.update({'type_container_template': type_container_template})
+        result.update({'csrf_token': csrf_token_value})
+        return render(request, 'new_project.html', result)
+
+
+@login_required
+def user_projects(request):
+    csrf_token_value = get_token(request)
+    user = CustomUser.objects.get(id=request.user.id)
+    projects = Project.objects.filter(owner=user).select_subclasses()
+
+    return render(request, 'projectlist.html', {
+        'projects': list(projects),
+        'csrf_token': csrf_token_value
+    })
+
+
+@login_required
+def open_project(request, project_id=None):
+    try:
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        project_overview = projects[0].get_overview_data()
+        prj_token = project_overview['type']
+        print request.COOKIES.keys()
+        return render(request, prj_token + '/' + prj_token + '_project_details.html',
+                      {'project_overview': project_overview, 'project_id': project_id})
+
+    except Exception as e:
+        print e
+        return render(request, 'error.html', {'error_msg': 'Error open project! Please retry.'})
+
+
+@login_required
+def delete_project(request, project_id=None):
+    if request.method == 'POST':
+
+        try:
+            Project.objects.filter(id=project_id).delete()
+            return redirect('projects:projects_list')
+        except Exception as e:
+            print e
+            return render(request, 'error.html', {'error_msg': 'Error deleting Project.'})
+
+    elif request.method == 'GET':
+        try:
+            projects = Project.objects.filter(id=project_id).select_subclasses()
+            project_overview = projects[0].get_overview_data()
+            prj_token = project_overview['type']
+            #                 example: 'etsi/etsi_project_delete.html'
+            print  prj_token + '/' + prj_token + '_project_delete.html', project_overview['name']
+            return render(request, prj_token + '/' + prj_token + '_project_delete.html',
+                          {'project_id': project_id, 'project_name': project_overview['name']})
+
+        except Exception as e:
+            print e
+            return render(request, 'error.html', {'error_msg': 'Project not found.'})
+
+
+
+@login_required
+def show_descriptors(request, project_id=None, descriptor_type=None):
+    csrf_token_value = get_token(request)
+    projects = Project.objects.filter(id=project_id).select_subclasses()
+    project_overview = projects[0].get_overview_data()
+    prj_token = project_overview['type']
+
+    page = prj_token + '/' + prj_token + '_project_descriptors.html'
+
+    return render(request, page, {
+        'descriptors': projects[0].get_descriptors(descriptor_type),
+        'project_id': project_id,
+        'project_type': prj_token,
+        'project_overview_data': project_overview,
+        "csrf_token_value": csrf_token_value,
+        'descriptor_type': descriptor_type
+    })
+
+
+@login_required
+def graph(request, project_id=None):
+    if request.method == 'GET':
+        csrf_token_value = get_token(request)
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        project_overview = projects[0].get_overview_data()
+        prj_token = project_overview['type']
+        # example : 'etsi/project_graph.html'
+        return render(request, prj_token + '/project_graph.html', {
+            'project_id': project_id,
+            'project_overview_data': projects[0].get_overview_data(),
+            'collapsed_sidebar': False
+        })
+
+
+@login_required
+def graph_data(request, project_id=None, descriptor_id=None):
+    print 'graph_data', project_id, descriptor_id
+    projects = Project.objects.filter(id=project_id).select_subclasses()
+    project_overview = projects[0].get_overview_data()
+    # data = projects[0].get_overview_data()
+    prj_token = project_overview['type']
+
+    topology = projects[0].get_graph_data_json_topology(descriptor_id)
+    response = HttpResponse(topology, content_type="application/json")
+    response["Access-Control-Allow-Origin"] = "*"
+
+    return response
+
+
+@login_required
+def delete_descriptor(request, project_id=None, descriptor_type=None, descriptor_id=None):
+    csrf_token_value = get_token(request)
+    projects = Project.objects.filter(id=project_id).select_subclasses()
+    result = projects[0].delete_descriptor(descriptor_type, descriptor_id)
+    project_overview = projects[0].get_overview_data()
+    prj_token = project_overview['type']
+    page = prj_token + '/' + prj_token + '_project_descriptors.html'
+
+    return render(request, page, {
+        'descriptors': projects[0].get_descriptors(descriptor_type),
+        'project_id': project_id,
+        'project_overview_data': project_overview,
+        "csrf_token_value": csrf_token_value,
+        'descriptor_type': descriptor_type,
+        #'alert_message': {
+        #    'success': result,
+        #    'message': "Delete succeeded!" if result else 'Error in delete'}
+    })
+
+
+@login_required
+def clone_descriptor(request, project_id=None, descriptor_type=None, descriptor_id=None):
+    csrf_token_value = get_token(request)
+    projects = Project.objects.filter(id=project_id).select_subclasses()
+    new_id = request.GET.get('newid', '')
+    result = projects[0].clone_descriptor(descriptor_type, descriptor_id, new_id)
+    project_overview = projects[0].get_overview_data()
+    prj_token = project_overview['type']
+    page = prj_token + '/' + prj_token + '_project_descriptors.html'
+
+    return render(request, page, {
+        'descriptors': projects[0].get_descriptors(descriptor_type),
+        'project_id': project_id,
+        'project_overview_data': project_overview,
+        "csrf_token_value": csrf_token_value,
+        'descriptor_type': descriptor_type,
+        'alert_message': {
+            'success': result,
+            'message': "Cloned!" if result else 'Error in cloning'}
+    })
+
+
+@login_required
+def new_descriptor(request, project_id=None, descriptor_type=None):
+    projects = Project.objects.filter(id=project_id).select_subclasses()
+    project_overview = projects[0].get_overview_data()
+    prj_token = project_overview['type']
+    page = prj_token + '/descriptor/descriptor_new.html'
+    if request.method == 'GET':
+        request_id = request.GET.get('id', '')
+
+        json_template = projects[0].get_new_descriptor(descriptor_type, request_id)
+        print 'new descriptor GET', json_template
+
+        descriptor_string_yaml = Util.json2yaml(json_template)
+        descriptor_string_json = json.dumps(json_template)
+
+        return render(request, page, {
+            'project_id': project_id,
+            'descriptor_type': descriptor_type,
+            'descriptor_id': request_id,
+            'project_overview_data': project_overview,
+            'descriptor_strings': {'descriptor_string_yaml': descriptor_string_yaml,
+                                   'descriptor_string_json': descriptor_string_json}
+        })
+    elif request.method == 'POST':
+        csrf_token_value = get_token(request)
+        data_type = request.POST.get('type')
+        print "TYPE", data_type
+        if data_type == "file":
+            file_uploaded = request.FILES['file']
+            text = file_uploaded.read()
+            data_type = file_uploaded.name.split(".")[-1]
+            desc_name = file_uploaded.name.split(".")[0]
+            result = projects[0].create_descriptor(desc_name, descriptor_type, text, data_type, file_uploaded)
+        else:
+            text = request.POST.get('text')
+            desc_name = request.POST.get('id')
+            result = projects[0].create_descriptor(desc_name, descriptor_type, text, data_type)
+
+
+        response_data = {
+            'project_id': project_id,
+            'descriptor_type': descriptor_type,
+            'project_overview_data': projects[0].get_overview_data(),
+            'descriptor_id': result,
+            'alert_message': {
+                'success': True if result != False else False,
+                'message': "Descriptor created" if result else 'Error in creation'}
+        }
+        status_code = 200 if result != False else 500
+        response = HttpResponse(json.dumps(response_data), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def edit_descriptor(request, project_id=None, descriptor_id=None, descriptor_type=None):
+    if request.method == 'POST':
+        print "edit_descriptor"
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].edit_descriptor(descriptor_type, descriptor_id, request.POST.get('text'),
+                                             request.POST.get('type'))
+        response_data = {
+            'project_id': project_id,
+            'descriptor_type': descriptor_type,
+            #'project_overview_data': projects[0].get_overview_data(),
+            'alert_message': {
+                'success':  True if result else False,
+                'message': "Descriptor modified." if result else 'Error during descriptor editing.'}
+        }
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps(response_data), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+    elif request.method == 'GET':
+        csrf_token_value = get_token(request)
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        project_overview = projects[0].get_overview_data()
+        print project_overview
+        prj_token = project_overview['type']
+        page = prj_token + '/descriptor/descriptor_view.html'
+
+        descriptor = projects[0].get_descriptor(descriptor_id, descriptor_type)
+
+        descriptor_string_json = json.dumps(descriptor)
+        descriptor_string_yaml = Util.json2yaml(descriptor)
+        # print descriptor
+        return render(request, page, {
+            'project_id': project_id,
+            'descriptor_id': descriptor_id,
+            'project_overview_data': projects[0].get_overview_data(),
+            'descriptor_type': descriptor_type,
+            'descriptor_strings': {'descriptor_string_yaml': descriptor_string_yaml,
+                                   'descriptor_string_json': descriptor_string_json}})
+
+
+@login_required
+def graph_positions(request, project_id=None):
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].edit_graph_positions(json.loads(request.POST.get('positions')))
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def add_element(request, project_id=None):
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].get_add_element(request)
+
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def remove_element(request, project_id=None):
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].get_remove_element(request)
+
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def add_link(request, project_id=None):
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].get_add_link(request)
+
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def remove_link(request, project_id=None):
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].get_remove_link(request)
+
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+@login_required
+def get_available_nodes(request, project_id=None):
+    if request.method == 'GET':
+        csrf_token_value = get_token(request)
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        print "get_available_nodes", request.GET.dict()
+        result = projects[0].get_available_nodes(request.GET.dict())
+        status_code = 500 if result == None else 200
+        print json.dumps(result)
+        response = HttpResponse(json.dumps(result), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+@login_required
+def overviewelement(request, project_id=None):
+    if request.method == 'GET':
+        result = {}
+        error_msg = None
+        try:
+            projects = Project.objects.filter(id=project_id).select_subclasses()
+            project = projects[0]
+            parameters = request.GET.dict()
+            print "parameters", parameters
+            result = project.get_node_overview(**parameters)
+        except Exception as e:
+            error_msg = str(e)
+
+        if error_msg is not None:
+            return JsonResponse({'error': {'error_msg': str(error_msg)}})
+
+        return JsonResponse({'node_overview': result})
+
+# ETSI specific method #
+@login_required
+def add_node_to_vnffg(request, project_id=None):
+    print "add_node_to_vnffg"  # TODO log
+    if request.method == 'POST':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].add_node_to_vnffg(request)
+
+        status_code = 200 if result else 500
+        response = HttpResponse(json.dumps({}), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+
+@login_required
+def unused_vnf(request, project_id=None, nsd_id=None):
+    if request.method == 'GET':
+        print 'in method unused_vnf : ', project_id, nsd_id  # TODO log
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        result = projects[0].get_unused_vnf(nsd_id)
+        status_code = 500 if result == None else 200
+        response = HttpResponse(json.dumps(result), content_type="application/json", status=status_code)
+        response["Access-Control-Allow-Origin"] = "*"
+        return response
+
+# end ETSI specific method #
+
+
+# OSM specific method #
+def get_package_files_list(request, project_id, project, descriptor_id, descriptor_type):
+    files_list = []
+    try:
+        files_list = project.get_package_files_list(descriptor_type, descriptor_id)
+        result = {'files': files_list}
+    except Exception as e:
+        print e
+        url = 'error.html'
+        result = {'error_msg': 'Unknown error.'}
+    return __response_handler(request, result)
+
+
+def download_pkg(request, project_id, project, descriptor_id, descriptor_type):
+    tar_pkg = project.download_pkg(project, descriptor_id, descriptor_type)
+
+    response = HttpResponse(content_type="application/tgz")
+    response["Content-Disposition"] = "attachment; filename=osm_export.tar.gz"
+    response.write(tar_pkg.getvalue())
+    return response
+
+
+def create_ns(request, project_id, project, descriptor_id, descriptor_type):
+    files_list = []
+    try:
+        ns_data={
+          "nsName": request.POST.get('nsName', 'WithoutName'),
+          "nsDescription": request.POST.get('nsDescription', ''),
+          "nsdId": request.POST.get('nsdId', ''),
+          "vimAccountId": request.POST.get('vimAccountId', ''),
+          "ssh-authorized-key": [
+            {
+              request.POST.get('key-pair-ref', ''): request.POST.get('keyValue', '')
+            }
+          ]
+        }
+        #result = project.create_ns(descriptor_type, descriptor_id, ns_data)
+
+    except Exception as e:
+        print e
+        url = 'error.html'
+        result = {'error_msg': 'Unknown error.'}
+    return __response_handler(request, result)
+
+# end OSM specific method #
+
+@login_required
+def custom_action(request, project_id=None, descriptor_id=None, descriptor_type=None, action_name=None):
+    if request.method == 'GET':
+        projects = Project.objects.filter(id=project_id).select_subclasses()
+        print "Custom action: " + action_name
+        return globals()[action_name](request, project_id, projects[0], descriptor_id, descriptor_type)
+
+
+def __response_handler(request, data_res, url=None, to_redirect=None, *args, **kwargs):
+    raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',')
+    if 'application/json' in raw_content_types:
+        return JsonResponse(data_res)
+    elif to_redirect:
+        return redirect(url, *args, **kwargs)
+    else:
+        return render(request, url, data_res)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644 (file)
index 0000000..99d35b5
--- /dev/null
@@ -0,0 +1,16 @@
+decorator==4.0.10
+Django==1.10.1
+django-model-utils==2.6
+functools32==3.2.3.post2
+jsonfield==1.0.3
+jsonschema==2.5.1
+pbr==1.10.0
+pyaml==15.8.2
+pymongo==3.4.0
+python-dateutil==2.6.0
+PyYAML==3.12
+requests==2.12.4
+six==1.10.0
+smmap2==2.0.3
+url==0.2.0
+wheel==0.24.0
diff --git a/sf_t3d/__init__.py b/sf_t3d/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sf_t3d/context_processor.py b/sf_t3d/context_processor.py
new file mode 100644 (file)
index 0000000..d554515
--- /dev/null
@@ -0,0 +1,10 @@
+from django.conf import settings  # import the settings file
+
+
+def conf_constants(request):
+    # return the value you want as a dictionnary. you may add multiple values in there.
+    return {
+        'SITE_NAME': settings.SITE_NAME,
+        'SHORT_SITE_NAME': settings.SHORT_SITE_NAME,
+        'VERSION': settings.VERSION,
+    }
\ No newline at end of file
diff --git a/sf_t3d/settings.py b/sf_t3d/settings.py
new file mode 100644 (file)
index 0000000..1020acd
--- /dev/null
@@ -0,0 +1,168 @@
+"""
+Django settings for sf_t3d project.
+
+Generated by 'django-admin startproject' using Django 1.9.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.9/ref/settings/
+"""
+
+import os
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'o5+o2jv(3-dqr(&ia#-@79cgr%xi*s+6xjws^8cxp211ge#buf'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+AUTH_USER_MODEL = "sf_user.CustomUser"
+
+SITE_NAME = "OSM Light Weight"
+SHORT_SITE_NAME = "OSM-LW-UI"
+
+LOGIN_URL = '/auth/'
+LOGOUT_URL = '/auth/'
+
+VERSION = "0.0.1"
+
+
+# Application definition
+INSTALLED_APPS = [
+    'django.contrib.admin',
+    'django.contrib.auth',
+    'django.contrib.contenttypes',
+    'django.contrib.messages',
+    'django.contrib.staticfiles',
+    'sf_user',
+    'projecthandler',
+    'vimhandler',
+    'instancehandler'
+]
+
+MIDDLEWARE_CLASSES = [
+    'django.middleware.security.SecurityMiddleware',
+    'django.contrib.sessions.middleware.SessionMiddleware',
+    'django.middleware.common.CommonMiddleware',
+    'django.middleware.csrf.CsrfViewMiddleware',
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
+    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+    'django.contrib.messages.middleware.MessageMiddleware',
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
+
+]
+
+SESSION_ENGINE='sf_user.sessions'
+SESSION_COOKIE_AGE = 3500 #25 min
+SESSION_EXPIRE_AT_BROWSER_CLOSE = True
+SESSION_SAVE_EVERY_REQUEST = True
+
+ROOT_URLCONF = 'sf_t3d.urls'
+
+TEMPLATES = [
+    {
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
+        'DIRS': [
+            os.path.join(BASE_DIR, 'template'),
+            os.path.join(BASE_DIR, 'projecthandler', 'template'),
+            os.path.join(BASE_DIR, 'projecthandler', 'template', 'download'),
+            os.path.join(BASE_DIR, 'projecthandler', 'template', 'project'),
+            os.path.join(BASE_DIR, 'vimhandler', 'template'),
+            os.path.join(BASE_DIR, 'instancehandler', 'template'),
+        ],
+        'APP_DIRS': True,
+        'OPTIONS': {
+            'context_processors': [
+                'django.template.context_processors.debug',
+                'django.template.context_processors.request',
+                'django.contrib.auth.context_processors.auth',
+                'django.contrib.messages.context_processors.messages',
+                'sf_t3d.context_processor.conf_constants',
+            ],
+            'libraries':{
+                'get': 'sf_t3d.templatetags.get',
+
+            }
+        },
+    },
+]
+
+WSGI_APPLICATION = 'sf_t3d.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+
+DATABASES = {
+    'default': {
+        'ENGINE': 'django.db.backends.sqlite3',
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+    },
+    {
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.9/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.9/howto/static-files/
+
+STATIC_URL = '/static/'
+
+STATICFILES_FINDERS = (
+    'django.contrib.staticfiles.finders.FileSystemFinder',
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
+    'django.contrib.staticfiles.finders.DefaultStorageFinder'
+)
+
+STATICFILES_DIRS = (
+    # Put strings here, like "/home/html/static" or "C:/www/django/static".
+    # Always use forward slashes, even on Windows.
+    # Don't forget to use absolute paths, not relative paths.
+    os.path.join(BASE_DIR, "static"),
+)
+
+LOCALE_PATHS = (
+    os.path.join(BASE_DIR, 'locale'),
+)
\ No newline at end of file
diff --git a/sf_t3d/templatetags/__init__.py b/sf_t3d/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sf_t3d/templatetags/get.py b/sf_t3d/templatetags/get.py
new file mode 100644 (file)
index 0000000..80d3695
--- /dev/null
@@ -0,0 +1,37 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django import template
+register = template.Library()
+
+'''
+    Custom template filter 
+'''
+@register.filter
+def get(mapping, key):
+    result = mapping.get(key, '')
+    return result
+
+'''
+    Custom template filter 
+'''
+@register.filter
+def get_sub(mapping, args):
+    splitted = args.split(',')
+    sub_dict = mapping.get(splitted[0], '')
+    if isinstance(sub_dict, dict):
+        return sub_dict.get(splitted[1], '')
+    return ''
diff --git a/sf_t3d/urls.py b/sf_t3d/urls.py
new file mode 100644 (file)
index 0000000..0784ed1
--- /dev/null
@@ -0,0 +1,35 @@
+"""sf_t3d URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.9/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Add an import:  from blog import urls as blog_urls
+    2. Import the include() function: from django.conf.urls import url, include
+    3. Add a URL to urlpatterns:  url(r'^blog/', include(blog_urls))
+"""
+from django.conf.urls import url, include
+from django.contrib import admin
+from sf_user import views as user_views
+from sf_t3d import views
+
+app_name = 'base'
+urlpatterns = [
+    url(r'^admin/', admin.site.urls),
+    url(r'^auth/$', user_views.login_view, name='auth_user'),
+    url(r'^auth_guest/$', user_views.guest_login, name='auth_user_guest'),
+    url(r'^register', user_views.register_view, name='register_user'),
+    url(r'^projects/', include('projecthandler.urls.project', namespace='projects'), name='projects_base'),
+    url(r'^vims/', include('vimhandler.urls', namespace='vim'), name='vims_base'),
+
+    url(r'^$', views.home, name='home'),
+    url(r'^home', views.home, name='home'),
+    url(r'^forbidden', views.forbidden, name='forbidden'),
+
+]
diff --git a/sf_t3d/views.py b/sf_t3d/views.py
new file mode 100644 (file)
index 0000000..91590f0
--- /dev/null
@@ -0,0 +1,22 @@
+from django.shortcuts import render
+from django.contrib.auth.decorators import login_required
+
+from projecthandler.models import Project
+from sf_user.models import CustomUser
+
+
+@login_required
+def home(request):
+    user = CustomUser.objects.get(id=request.user.id)
+    projects = Project.objects.filter(owner=user).select_subclasses()
+    result = {
+        'projects': len(projects) if projects else 0,
+    }
+    return render(request, 'home.html', result)
+
+
+def forbidden(request):
+    return render(request, 'forbidden.html')
+
+
+
diff --git a/sf_t3d/wsgi.py b/sf_t3d/wsgi.py
new file mode 100644 (file)
index 0000000..c52ee6f
--- /dev/null
@@ -0,0 +1,16 @@
+"""
+WSGI config for sf_t3d project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "sf_t3d.settings")
+
+application = get_wsgi_application()
diff --git a/sf_user/__init__.py b/sf_user/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sf_user/admin.py b/sf_user/admin.py
new file mode 100644 (file)
index 0000000..2e9690a
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.contrib import admin
+
+# Register your models here.
diff --git a/sf_user/apps.py b/sf_user/apps.py
new file mode 100644 (file)
index 0000000..902aa7d
--- /dev/null
@@ -0,0 +1,21 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.apps import AppConfig
+
+
+class SfUserConfig(AppConfig):
+    name = 'sf_user'
diff --git a/sf_user/management/__init__.py b/sf_user/management/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sf_user/management/commands/__init__.py b/sf_user/management/commands/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/sf_user/management/commands/clean_guest_data.py b/sf_user/management/commands/clean_guest_data.py
new file mode 100644 (file)
index 0000000..0ff087b
--- /dev/null
@@ -0,0 +1,19 @@
+from django.core.management.base import BaseCommand, CommandError
+from sf_user.models import CustomSession, CustomUser
+
+
+class Command(BaseCommand):
+    help = 'Clean Guest Users Sessions'
+
+    #def add_arguments(self, parser):
+    #    parser.add_argument('poll_id', nargs='+', type=int)
+
+    def handle(self, *args, **options):
+        #for guest in CustomUser.objects.filter(is_guest_user="True"):
+        #    self.stdout.write(self.style.SUCCESS(guest.username))
+            try:
+                CustomUser.objects.filter(is_guest_user="True").delete()
+            except Exception:
+                raise CommandError('Error unable to clean guest users sessions')
+
+            self.stdout.write(self.style.SUCCESS('Successfully cleaned guest users sessions'))
diff --git a/sf_user/management/commands/clearsessions.py b/sf_user/management/commands/clearsessions.py
new file mode 100644 (file)
index 0000000..8717521
--- /dev/null
@@ -0,0 +1,17 @@
+from django.core.management.base import BaseCommand, CommandError
+from sf_user.models import CustomSession
+
+
+class Command(BaseCommand):
+    help = 'Clean Users Sessions'
+
+    #def add_arguments(self, parser):
+    #    parser.add_argument('poll_id', nargs='+', type=int)
+
+    def handle(self, *args, **options):
+            try:
+                CustomSession.objects.all().delete()
+            except Exception:
+                raise CommandError('Error unable to clean users sessions')
+
+            self.stdout.write(self.style.SUCCESS('Successfully cleaned users sessions'))
\ No newline at end of file
diff --git a/sf_user/models.py b/sf_user/models.py
new file mode 100644 (file)
index 0000000..6485e82
--- /dev/null
@@ -0,0 +1,195 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.contrib.auth.models import (
+    AbstractBaseUser, BaseUserManager, PermissionsMixin)
+from django.utils import timezone
+from django.utils.translation import ugettext_lazy as _
+from django.contrib.sessions.base_session import AbstractBaseSession
+from django.db import models
+
+class CustomUserManager(BaseUserManager):
+    """Custom manager for CustomUser."""
+
+    def _create_user(self, username, password, is_staff, is_superuser, **extra_fields):
+        """Create and save a CustomUser with the given username and password. """
+        now = timezone.now()
+
+        if not username:
+            raise ValueError('The given username must be set')
+
+        is_active = extra_fields.pop("is_active", True)
+        user = self.model(username=username, is_staff=is_staff, is_active=is_active,
+                          is_superuser=is_superuser, last_login=now,
+                          date_joined=now, **extra_fields)
+        user.set_password(password)
+        user.save(using=self._db)
+        return user
+
+    """Create and save an CustomUser with the given username and password."""
+    def create_superuser(self, username, password, **extra_fields):
+        return self._create_user(username, password, True, True, is_admin=True,
+                                 **extra_fields)
+
+    """Create and save an FullOperator with the given email and password. """
+    def create_full_operator(self, username, password=None, **extra_fields):
+        return self._create_user(username, password, False, False, is_full_operator=True,
+                                 **extra_fields)
+
+    """Create and save an BasicUser with the given email and password. """
+    def create_basic_user(self, username, password=None, **extra_fields):
+        return self._create_user(username, password, False, False, is_basic_user=True,
+                                 **extra_fields)
+
+    """Create and save an GuestUser with the given email and password. """
+    def create_guest_user(self, username, password="guest", **extra_fields):
+        return self._create_user(username, password, False, False, is_guest_user=True,
+                                 **extra_fields)
+
+
+class AbstractCustomUser(AbstractBaseUser, PermissionsMixin):
+    """Abstract User with the same behaviour as Django's default User.
+
+    AbstractCustomUser does not have username field. Uses email as the
+    USERNAME_FIELD for authentication.
+
+    Use this if you need to extend EmailUser.
+
+    Inherits from both the AbstractBaseUser and PermissionMixin.
+
+    The following attributes are inherited from the superclasses:
+        * password
+        * last_login
+        * is_superuser
+
+    """
+    username = models.CharField(_('username'), max_length=255, unique=True, db_index=True)
+
+    email = models.EmailField(_('email address'), max_length=255, unique=True)
+
+    first_name = models.CharField(_('first name'), max_length=255, blank=True)
+    last_name = models.CharField(_('last name'), max_length=255, blank=True)
+    phone = models.CharField(_('phone'), max_length=100, blank=True)
+    # user_groups = models.ManyToManyField(UserGroup, verbose_name=_('user groups'), blank=True)
+
+    is_admin = models.BooleanField(_('admin status'), default=False)
+    is_full_operator = models.BooleanField(_('full_operator status'), default=False)
+    is_basic_user = models.BooleanField(_('basic_user status'), default=False)
+    is_guest_user = models.BooleanField(_('guest_user status'), default=False)
+
+    is_staff = models.BooleanField(
+        _('staff status'), default=False, help_text=_(
+            'Designates whether the user can log into this admin site.'))
+    is_active = models.BooleanField(_('active'), default=True, help_text=_(
+        'Designates whether this user should be treated as '
+        'active. Unselect this instead of deleting accounts.'))
+
+    date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
+
+    objects = CustomUserManager()
+
+    USERNAME_FIELD = 'username'
+    REQUIRED_FIELDS = ['email', 'first_name', 'last_name', 'phone', ]
+
+    class Meta:
+        verbose_name = _('custom user')
+        verbose_name_plural = _('custom users')
+        abstract = True
+
+    def get_full_name(self):
+        """Return the fullname."""
+        return "%s %s" % (self.last_name, self.first_name)
+
+    def get_short_name(self):
+        """Return the firstname."""
+        return self.first_name
+
+
+class CustomUser(AbstractCustomUser):
+    """
+    Concrete class of AbstractCustomUser.
+
+    Use this if you don't need to extend CustomUser.
+
+    """
+
+    class Meta(AbstractCustomUser.Meta):
+        swappable = 'AUTH_USER_MODEL'
+
+    def count_admins(self):
+        return CustomUser.objects.filter(is_admin=True).count()
+
+    def count_employee(self):
+        return CustomUser.objects.filter(is_full_operator=True).count()
+
+    def count_basic_users(self):
+        return CustomUser.objects.filter(is_basic_user=True).count()
+
+    def count_inactives(self):
+        return CustomUser.objects.filter(is_active=False).count()
+
+    def is_guest(self):
+        return self.is_guest_user
+
+    def get_avatar(self):
+        if self.is_admin:
+            return "assets/img/employer.png"
+        elif self.is_full_operator:
+            return "assets/img/employer.png"
+        elif self.is_basic_user:
+            return "assets/img/employer.png"
+        elif self.is_guest_user:
+            return "assets/img/account_circle.png"
+
+    def get_user_role(self):
+        if self.is_admin:
+            return 0, "Admin"
+        elif self.is_full_operator:
+            return 1, "Full operator"
+        elif self.is_basic_user:
+            return 2, "Basic user"
+        elif self.is_guest_user:
+            return 3, "Guest user"
+
+    def get_user_role_name(self):
+        if self.is_admin:
+            return "Admin"
+        elif self.is_full_operator:
+            return "Full operator"
+        elif self.is_basic_user:
+            return "Basic user"
+        elif self.is_guest_user:
+            return "Guest user"
+
+    def has_perm(self, perm, obj=None):
+        if perm == 'deploymenthandler':
+            if self.is_guest_user:
+                return False
+            else:
+                return True
+        else:
+            super.has_perm(perm, obj)
+
+
+class CustomSession(AbstractBaseSession):
+    account_id = models.IntegerField(null=True, db_index=True)
+
+    @classmethod
+    def get_session_store_class(cls):
+        return SessionStore
+
diff --git a/sf_user/sessions.py b/sf_user/sessions.py
new file mode 100644 (file)
index 0000000..df4ed71
--- /dev/null
@@ -0,0 +1,32 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.contrib.sessions.backends.db import SessionStore as DBStore
+import sf_user
+
+class SessionStore(DBStore):
+    @classmethod
+    def get_model_class(cls):
+        return sf_user.models.CustomSession
+
+    def create_model_instance(self, data):
+        obj = super(SessionStore, self).create_model_instance(data)
+        try:
+            account_id = int(data.get('_auth_user_id'))
+        except (ValueError, TypeError):
+            account_id = None
+        obj.account_id = account_id
+        return obj
\ No newline at end of file
diff --git a/sf_user/tests.py b/sf_user/tests.py
new file mode 100644 (file)
index 0000000..79947e6
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.test import TestCase
+
+# Create your tests here.
diff --git a/sf_user/views.py b/sf_user/views.py
new file mode 100644 (file)
index 0000000..fb33347
--- /dev/null
@@ -0,0 +1,89 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.shortcuts import render
+from django.contrib.auth import login, logout, authenticate
+from django.http import HttpResponseRedirect
+from sf_user.models import CustomUser
+import urllib
+import uuid
+
+
+# Create your views here.
+def login_view(request):
+    if hasattr(request.user, "is_guest_user") and request.user.is_guest_user == True:
+        print "is_guest", request.user.is_guest_user
+        CustomUser.objects.get(id=request.user.id).delete()
+    logout(request)
+    extra_data = {}
+    next_page = ""
+    if request.GET:
+        next_page = request.GET['next']
+    error_message = ''
+    if request.POST:
+        print request.POST.get('username')
+        print request.POST.get('password')
+        next_page = request.POST.get('next')
+        next_page = urllib.unquote(next_page).decode('iso-8859-2')
+        user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
+        print "Auth Result: " + str(user) + " -> " + str(user)
+        if user and user.is_active:
+            if user.is_authenticated():
+                login(request, user)
+                print next_page
+                if next_page == "" or next_page is None:
+                    return HttpResponseRedirect('/home')
+                else:
+                    return HttpResponseRedirect(next_page)
+        else:
+            error_message = 'Login failed!'
+    return render(request, 'login.html', {'error_message':error_message, 'collapsed_sidebar': False})
+
+
+def guest_login(request):
+    #user = CustomUser.objects.get(id=request.user.id)
+    if hasattr(request.user, "is_guest_user") and request.user.is_guest_user == True:
+        CustomUser.objects.get(id=request.user.id).delete()
+    logout(request)
+    next = ""
+
+    guest_user_name = "Guest_"+str(uuid.uuid4())
+    guest_user_email = guest_user_name+"@guest.it"
+    guest_user = CustomUser.objects.create(username=guest_user_name, is_guest_user="True", email=guest_user_email, first_name='User', last_name='Guest')
+    print guest_user.username
+
+    if guest_user and guest_user.is_active:
+        if guest_user.is_authenticated():
+            login(request, guest_user)
+            if next == "":
+                return HttpResponseRedirect('/home')
+            else:
+                return HttpResponseRedirect(next)
+
+    return render(request, 'login.html', {'error_message': 'New Guest session failed.'})
+
+
+def register_view(request):
+
+    logout(request)
+    extra_data = {}
+    next = ""
+    if request.GET:
+        next = request.GET['next']
+    error_message = ''
+    if request.POST:
+        print "new user"
+    return render(request, 'register_user.html', {'error_message': error_message, 'collapsed_sidebar': False})
\ No newline at end of file
diff --git a/static/assets/img/OSM-logo.png b/static/assets/img/OSM-logo.png
new file mode 100644 (file)
index 0000000..7de447c
Binary files /dev/null and b/static/assets/img/OSM-logo.png differ
diff --git a/static/assets/img/account_circle.png b/static/assets/img/account_circle.png
new file mode 100644 (file)
index 0000000..07643f9
Binary files /dev/null and b/static/assets/img/account_circle.png differ
diff --git a/static/assets/img/ce.png b/static/assets/img/ce.png
new file mode 100755 (executable)
index 0000000..b237a57
Binary files /dev/null and b/static/assets/img/ce.png differ
diff --git a/static/assets/img/cloudnode.png b/static/assets/img/cloudnode.png
new file mode 100755 (executable)
index 0000000..2e748c2
Binary files /dev/null and b/static/assets/img/cloudnode.png differ
diff --git a/static/assets/img/controller-256.png b/static/assets/img/controller-256.png
new file mode 100755 (executable)
index 0000000..34b009b
Binary files /dev/null and b/static/assets/img/controller-256.png differ
diff --git a/static/assets/img/cp-80.png b/static/assets/img/cp-80.png
new file mode 100755 (executable)
index 0000000..3e2ad0e
Binary files /dev/null and b/static/assets/img/cp-80.png differ
diff --git a/static/assets/img/docker.png b/static/assets/img/docker.png
new file mode 100644 (file)
index 0000000..3e5b8b3
Binary files /dev/null and b/static/assets/img/docker.png differ
diff --git a/static/assets/img/employer.jpg b/static/assets/img/employer.jpg
new file mode 100755 (executable)
index 0000000..c349da7
Binary files /dev/null and b/static/assets/img/employer.jpg differ
diff --git a/static/assets/img/employer.png b/static/assets/img/employer.png
new file mode 100644 (file)
index 0000000..5ef83a7
Binary files /dev/null and b/static/assets/img/employer.png differ
diff --git a/static/assets/img/euh.png b/static/assets/img/euh.png
new file mode 100755 (executable)
index 0000000..f6a756e
Binary files /dev/null and b/static/assets/img/euh.png differ
diff --git a/static/assets/img/favicon.ico b/static/assets/img/favicon.ico
new file mode 100644 (file)
index 0000000..c588094
Binary files /dev/null and b/static/assets/img/favicon.ico differ
diff --git a/static/assets/img/host-256.png b/static/assets/img/host-256.png
new file mode 100755 (executable)
index 0000000..df1995c
Binary files /dev/null and b/static/assets/img/host-256.png differ
diff --git a/static/assets/img/host.png b/static/assets/img/host.png
new file mode 100755 (executable)
index 0000000..df1995c
Binary files /dev/null and b/static/assets/img/host.png differ
diff --git a/static/assets/img/k8s.png b/static/assets/img/k8s.png
new file mode 100644 (file)
index 0000000..922d33f
Binary files /dev/null and b/static/assets/img/k8s.png differ
diff --git a/static/assets/img/l2sw.png b/static/assets/img/l2sw.png
new file mode 100755 (executable)
index 0000000..86bff2c
Binary files /dev/null and b/static/assets/img/l2sw.png differ
diff --git a/static/assets/img/ofcontroller.png b/static/assets/img/ofcontroller.png
new file mode 100755 (executable)
index 0000000..34b009b
Binary files /dev/null and b/static/assets/img/ofcontroller.png differ
diff --git a/static/assets/img/ofl2sw.png b/static/assets/img/ofl2sw.png
new file mode 100755 (executable)
index 0000000..90c62ad
Binary files /dev/null and b/static/assets/img/ofl2sw.png differ
diff --git a/static/assets/img/osm_logo.png b/static/assets/img/osm_logo.png
new file mode 100644 (file)
index 0000000..fbc33a3
Binary files /dev/null and b/static/assets/img/osm_logo.png differ
diff --git a/static/assets/img/osm_logo.svg b/static/assets/img/osm_logo.svg
new file mode 100644 (file)
index 0000000..5f5033e
--- /dev/null
@@ -0,0 +1 @@
+<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2674.87 967.28"><defs><style>.cls-1{fill:#397ec1;}.cls-2{fill:#e83a7a;}.cls-3{fill:#704fa0;}.cls-4{fill:#64cae1;}.cls-5{fill:#9c59a4;}</style></defs><title>osm-logo_color_rgb</title><path d="M1459.74,1460.74,1380,1636.65a36.5,36.5,0,0,1-8.55,8.55c-3.32,2.39-6.39,3.56-9.26,3.56h-4.27c-2.83,0-6-1.17-9.61-3.56a25.66,25.66,0,0,1-8.2-8.55l-79.76-175.91v381.73a21.19,21.19,0,0,1-5.35,14.59q-5.32,6.08-15.29,6.06-7.84,0-14.26-6.06a19.36,19.36,0,0,1-6.39-14.59V1368.14a19.35,19.35,0,0,1,6.39-14.58c4.29-4,9-6.06,14.26-6.06q15,0,20.64,15.67l100.42,218.63,99-218.63q5.7-15.67,19.94-15.67a20.64,20.64,0,0,1,20.64,20.64v474.33a20.64,20.64,0,0,1-20.64,20.66q-9.26,0-14.59-6.06a21.27,21.27,0,0,1-5.35-14.59Z" transform="translate(-79.91 -933.74)"/><path d="M1909.28,1836.77a11.35,11.35,0,0,0,.35,3.2,8.08,8.08,0,0,1,.36,2.5c0,1,.24,1.42.71,1.42a19.15,19.15,0,0,1-5.33,13.53q-5.34,5.7-15.32,5.7a21.38,21.38,0,0,1-12.46-4.27,17.81,17.81,0,0,1-7.49-11.4l-39.16-156.69H1705.6l-39.18,156.69a21.7,21.7,0,0,1-7.84,11.4,19.84,19.84,0,0,1-12.11,4.27q-10,0-15.31-6.41a21.7,21.7,0,0,1-5.35-14.25v-3.56L1749,1362.45q5.67-15,20.66-15c3.8,0,7.34,1.55,10.67,4.64a25.78,25.78,0,0,1,7.14,11.75v-.71Zm-89.73-186.59-51.27-214.37-52,214.37Z" transform="translate(-79.91 -933.74)"/><path d="M2042.52,1371q0-7.83,6.39-14.25t14.26-6.43q12.82,0,20.64,18.52l201.57,392.42V1371q0-7.83,6-14.25a19.33,19.33,0,0,1,14.59-6.43q7.84,0,13.88,6.43T2326,1371v474.32q0,13.53-5.7,17.1c-3.8,2.36-6.65,3.56-8.55,3.56-9.51,0-15.91-1.42-19.23-4.27s-5.46-5.46-6.4-7.84l-203-393.84v385.29c0,6.66-1.9,11.74-5.7,15.32a20.07,20.07,0,0,1-14.23,5.33,21.86,21.86,0,0,1-14.26-5.33c-4.26-3.58-6.39-8.66-6.39-15.32Z" transform="translate(-79.91 -933.74)"/><path d="M2469.9,1492.78a149.71,149.71,0,0,1,10.32-55.91,132.59,132.59,0,0,1,29.55-45.22,144.94,144.94,0,0,1,45.59-30.61q26.31-11.42,58.39-11.41a133.19,133.19,0,0,1,54.83,11.41,147.27,147.27,0,0,1,44.88,30.61,140.5,140.5,0,0,1,30.26,45.57,144.2,144.2,0,0,1,11,56.27v229.33a139.59,139.59,0,0,1-11,55.18,140.83,140.83,0,0,1-75.14,75.5,137.12,137.12,0,0,1-54.83,11,139.72,139.72,0,0,1-55.2-11,153.26,153.26,0,0,1-45.94-29.92,141.08,141.08,0,0,1-31.34-43.45,122.76,122.76,0,0,1-11.38-52.33Zm41.3,231.47a88.41,88.41,0,0,0,8.55,38.1,107.5,107.5,0,0,0,22.43,31.69,104,104,0,0,0,32.4,21.37,97.76,97.76,0,0,0,38.46,7.84,91.69,91.69,0,0,0,38.1-8.2,110.12,110.12,0,0,0,32.05-21.72,108.09,108.09,0,0,0,22.43-31.69,88.42,88.42,0,0,0,8.55-38.1V1492.78a105.74,105.74,0,0,0-7.84-41,103.12,103.12,0,0,0-21.35-32.75,97.45,97.45,0,0,0-32.07-21.73,101.49,101.49,0,0,0-39.87-7.82,97.74,97.74,0,0,0-39.89,8.17,103.39,103.39,0,0,0-54.12,55.56,101.84,101.84,0,0,0-7.84,39.53Z" transform="translate(-79.91 -933.74)"/><path d="M1369.46,1108.69q0,43.55-19.06,67.32t-53.52,23.77q-34.1,0-53.5-23.77t-19.52-66.86V1060.2q0-43.4,19.3-67.42t53.44-24q34.57,0,53.72,24.08t19.14,67.8Zm-17.74-48.8q0-36.9-14.2-55.43t-40.92-18.54q-25.81,0-40.32,18.16t-14.83,54.26v50.35q0,37.06,14.67,55.67t40.76,18.6q26.88,0,40.85-18.54t14-55.73Z" transform="translate(-79.91 -933.74)"/><path d="M1526.81,1121q0,38.58-13.44,58.66t-40.14,20.09q-29,0-43.39-23.17v84.32h-17V1029.62h15.75l.78,22.86q14.84-25.93,43.39-25.93,27,0,40.46,19.14t13.58,58.52Zm-17.13-15.67q0-32.08-10-47.2t-31.1-15.12q-27.19,0-38.77,27.62v87.91q11.59,25.14,39.08,25.14,20.68,0,30.63-15.12t10.13-46.59Z" transform="translate(-79.91 -933.74)"/><path d="M1621.79,1199.79q-29.54,0-46.17-19.31t-17-55v-20.39q0-36.76,16.07-57.67t44.16-20.93q27.19,0,41.46,17.74t14.59,54.37v16.05h-99.28v9.42q0,28.71,12.27,44.32t34.67,15.59q24.69,0,40-18.08l8.94,11.44q-16.36,22.37-49.7,22.38m-2.94-157.2q-20.25,0-31.12,14.28t-12,41.79h82v-4.5q-1.23-51.57-38.91-51.57" transform="translate(-79.91 -933.74)"/><path d="M1727.7,1029.62l.46,26.56a62.14,62.14,0,0,1,18.93-21.62,44.6,44.6,0,0,1,26.17-8q43.71,0,44.78,58.22V1196.7h-17V1086.61q-.17-22.39-7.87-33.05t-24.27-10.65q-13.42,0-24.16,10.11t-16.29,27.72v116h-17.14V1029.62Z" transform="translate(-79.91 -933.74)"/><path d="M2044,1141.42q0-18.83-10.73-29.65t-40.08-20.69q-29.32-9.88-43.09-22.08-18-15.91-18-42,0-25.79,17.74-42t45.86-16.21q28.73,0,46.95,18.84t18.22,48.48H2043q0-22.7-12.73-36.68t-34.68-14q-21.3,0-33.59,11.51t-12.27,29.73q0,17,11.59,27.8t36.9,19.22q25.33,8.43,38.29,17.46t19.15,21.23q6.17,12.21,6.19,28.71,0,26.4-17.84,42.55t-47.19,16.15a79.65,79.65,0,0,1-36-8.12,60.64,60.64,0,0,1-25.48-23.32q-9.19-15.2-9.18-35.9h17.6q0,23.79,14.36,37.37t38.75,13.58q21.32,0,34.22-11.43t12.89-30.57" transform="translate(-79.91 -933.74)"/><path d="M2091.66,1104.67q0-36.14,16.37-57.13t44.64-21q28.4,0,44.69,20.69t16.45,57V1122q0,36.76-16.45,57.3T2153,1199.79q-27.95,0-44.32-20.07t-17-55.28Zm17.3,17.21q0,28.85,11.65,45.35t32.35,16.51q21.16,0,32.34-15.89t11.37-45.51v-17.73q0-28.85-11.74-45.43t-32.26-16.59q-19.92,0-31.66,16.21t-12,44.58Z" transform="translate(-79.91 -933.74)"/><path d="M2342.89,1176.78q-14.22,23-44,23-22.7,0-34.27-15.77t-11.75-46.32v-108.1h17v107.62q0,22.7,8,34.44t23.14,11.73q32.46,0,41.54-31.5v-122.3h17.3V1196.7h-16.67Z" transform="translate(-79.91 -933.74)"/><path d="M2471.07,1045.68a64.77,64.77,0,0,0-10.81-1.08,32.3,32.3,0,0,0-23.85,9.72q-9.66,9.74-13.68,27v115.36h-17.14V1029.62h16.84l.3,25q12.2-28.11,38.31-28.1,6.79,0,10.35,2Z" transform="translate(-79.91 -933.74)"/><path d="M2551,1183.73q17.9,0,27.48-10t10.35-28.78h16.53q-1.23,25.64-16.38,40.24t-38,14.59q-29.82,0-45-19.07t-15.51-56.61v-20.37q0-38.3,15.21-57.76t45.16-19.45q24.86,0,39.22,15.69T2605.4,1086h-16.53q-.78-20.68-10.49-32t-27.5-11.37q-22.22,0-32.66,15t-10.42,46v19q0,32.08,10.49,46.66t32.73,14.58" transform="translate(-79.91 -933.74)"/><path d="M2696.82,1199.79q-29.54,0-46.17-19.31t-17-55v-20.39q0-36.76,16.07-57.67t44.16-20.93q27.19,0,41.46,17.74t14.59,54.37v16.05h-99.28v9.42q0,28.71,12.27,44.32t34.67,15.59q24.69,0,40-18.08l8.94,11.44q-16.36,22.37-49.7,22.38m-2.94-157.2q-20.25,0-31.12,14.28t-12,41.79h82v-4.5q-1.23-51.57-38.91-51.57" transform="translate(-79.91 -933.74)"/><path class="cls-1" d="M684.83,976.31c14.21,2.11,28.57,3.61,42.63,6.51,12.88,2.66,25.55,6.55,38.15,10.43,10.67,3.29,21.29,6.87,31.58,11.13a350.93,350.93,0,0,1,63.09,34,368.08,368.08,0,0,1,103.84,106.12,361.45,361.45,0,0,1,44.12,98,345.89,345.89,0,0,1,11.86,66.59,370.86,370.86,0,0,1-2.44,79.72,12.81,12.81,0,0,0,0,1.74c-4.43,26.34-11.16,52-21.24,76.82a337,337,0,0,1-41.82,74.71,331.6,331.6,0,0,1-51.4,54.69,317.32,317.32,0,0,1-61.15,41c-26.05,13.31-53.28,23.39-82.2,29a323,323,0,0,1-59.07,6.22,391.45,391.45,0,0,1-41.71-2.45,254.47,254.47,0,0,1-59.18-13.23c-15-5.3-29.81-11.48-44.43-17.86-7-3.07-13.5-7.5-20.21-11.3,7.14,1.55,13.9-.24,20.7-2.15,34.16-9.58,66.23-24,97.24-41,35.79-19.64,70-41.68,100.89-68.46,3.09-2.68,54.64-59,55.88-60.9,5.76-8.77,12.09-17.25,17-26.5,8.17-15.51,16.19-31.2,22.81-47.41,7.09-17.41,11.67-35.73,15-54.31,3.18-17.87,5.45-35.87,5.19-54-.22-15.31-1.61-30.63-3.29-45.87-2.33-21.21-7.72-41.79-14.74-61.94a325.43,325.43,0,0,0-26.8-58c-10.38-17.65-21.91-34.57-35.76-49.61a501.78,501.78,0,0,0-37.94-37.06c-10.56-9.26-22.19-17.38-33.89-25.22-10.49-7-21.73-12.95-32.64-19.36" transform="translate(-79.91 -933.74)"/><path class="cls-2" d="M213,1694.87c-12.52-12.68-25.48-24.95-37.45-38.12-15.27-16.78-28-35.52-39.37-55.13a369,369,0,0,1-42.88-113.7,374,374,0,0,1-6.85-62.6,363,363,0,0,1,8.15-86.7,346.93,346.93,0,0,1,21.75-66.81A351.38,351.38,0,0,1,183.89,1170c1.68-1.77,3.2-3.7,4.8-5.56.35-.73.68-1.44,1-2.15l0,.06a19,19,0,0,0,3.32-1.9c10.24-8.86,19.94-18.41,30.72-26.56,32.42-24.49,67.75-43.56,106.93-55.08,23.21-6.84,46.76-11.75,70.95-13.31,16-1,32-1.52,48-.22a323.28,323.28,0,0,1,63.76,11.18c20.85,6,41.33,13.17,60.09,24.41,13.61,8.17,27.75,15.62,40.63,24.82a296.3,296.3,0,0,1,79.49,84.83c9.28,14.83,16.75,30.79,25,46.24-2.88-2.26-5.48-5.22-8.74-6.66-7.34-3.25-15-6-22.59-8.5-39.13-13-79.54-19.69-120.47-22.67-25.34-1.85-50.86-1.66-76.31-1.87-8.28-.06-16.56,1.55-24.85,2.41-12,2-24.22,3.28-36,6.14-16.46,4-32.72,8.86-48.9,13.91-6.65,2.06-12.84,5.56-19.23,8.41a33,33,0,0,0-4.5,1.43A315.08,315.08,0,0,0,240.15,1345a305.23,305.23,0,0,0-38.54,67.32c-5.71,13.79-10.61,28-15,42.23a151.77,151.77,0,0,0-5.07,26.15c-2.22,17.95-4.48,35.93-5.51,54-.65,11.41.33,23,1.55,34.44,1.52,14.21,3.37,28.48,6.27,42.45a314.4,314.4,0,0,0,22.81,68.27c2.28,4.94,4.24,10,6.35,15" transform="translate(-79.91 -933.74)"/><path class="cls-3" d="M535.23,1628c6.73,3.82,13.17,8.23,20.21,11.3,14.63,6.38,29.39,12.55,44.45,17.87a254.87,254.87,0,0,0,59.18,13.22,390.9,390.9,0,0,0,41.69,2.45,323.32,323.32,0,0,0,59.09-6.22c28.9-5.56,56.13-15.64,82.18-29a316.47,316.47,0,0,0,61.16-41A332.13,332.13,0,0,0,954.59,1542a336.69,336.69,0,0,0,41.82-74.71c10.08-24.79,16.81-50.48,21.24-76.82,2,10.73,4.15,21.43,5.81,32.21a204.25,204.25,0,0,1,2.55,24.22c.55,16.35,1.33,32.77.46,49.07a316.06,316.06,0,0,1-5.79,46.62c-3.53,17.25-8.55,34.25-13.8,51.1a264.61,264.61,0,0,1-13.87,36.2c-8.8,18.24-18.06,36.31-30.31,52.62-7.38,9.81-14.45,19.85-21.91,29.6-2.85,3.72-6.38,6.92-9.54,10.42-2.88,3.17-5.43,6.71-8.58,9.58-10.24,9.29-20.66,18.39-31.07,27.5-13.15,11.49-27.83,20.82-42.69,29.92a314.83,314.83,0,0,1-89.45,37.77c-10.84,2.68-21.83,4.72-32.75,7-4.13.81-8.23,2.06-12.39,2.36-14.45,1.06-28.92,2.23-43.4,2.5a228.87,228.87,0,0,1-30.52-1.63c-13.93-1.6-28-3-41.6-6.33-18.92-4.72-37.86-10-55.92-17.29-16.57-6.65-32.54-15.32-47.77-24.72a382.22,382.22,0,0,1-43.2-31.31c-17.41-14.45-32.39-31.42-45.87-49.67-19.77-26.78-35.54-55.64-45.71-87.34-3.91-12.16-7.22-24.55-10-37a246.49,246.49,0,0,1-6.39-58.54c.36-19.07,1.87-38.15,3.42-57.17.55-6.79,2.74-13.46,4.18-20.18.92,3.77,1.12,7.91,2.91,11.19a168.31,168.31,0,0,0,12.54,19.63A532.31,532.31,0,0,0,442.88,1561c11.18,9.86,22.19,20,34.1,29,14.67,11,30.14,21.05,45.37,31.34,3.56,2.41,7.53,4.18,11.32,6.25l1.44.48Z" transform="translate(-79.91 -933.74)"/><path class="cls-4" d="M718.63,1256.77c-8.29-15.45-15.75-31.42-25-46.24a296.51,296.51,0,0,0-79.48-84.84c-12.88-9.2-27-16.64-40.65-24.8-18.76-11.24-39.22-18.39-60.09-24.41a321.77,321.77,0,0,0-63.76-11.19c-16.05-1.28-32-.81-48,.22-24.17,1.58-47.72,6.49-70.95,13.33-39.18,11.52-74.49,30.6-106.93,55.08-10.78,8.14-20.48,17.7-30.72,26.56a20.7,20.7,0,0,1-3.32,1.9c4-8.58,7.76-17.29,12.08-25.69a359,359,0,0,1,65.75-89.56A369.78,369.78,0,0,1,462.77,945a357.85,357.85,0,0,1,83.94-5c29.13,1.57,57.84,6.38,85.49,15.83,15.91,5.45,31.36,12.17,47,18.33l5.62,2.17c10.91,6.41,22.14,12.33,32.64,19.36,11.7,7.82,23.33,16,33.87,25.22a499.07,499.07,0,0,1,38,37.06c13.68,14.83,25.07,31.52,35.35,48.93a324.66,324.66,0,0,1,26.85,57.6c7.19,20.48,12.74,41.39,15.1,63,1.68,15.23,3.07,30.57,3.29,45.87.25,18.11-2,36.11-5.19,54-3.31,18.58-7.9,36.9-15,54.31-6.6,16.23-14.63,31.91-22.79,47.42-4.88,9.24-11.21,17.71-17,26.48-.62.95-5,6.73-9.53,12.52s-36.2,37.5-37,38.45c0-8.17-2.85-106.42-7.39-129.37-6-27.64-12.33-55.16-21.86-81.82-4.53-12.66-10-25-15-37.48-.21-.36-.4-.73-.6-1.09Z" transform="translate(-79.91 -933.74)"/><path class="cls-5" d="M213,1694.87s0,0,0,0l0,0Z" transform="translate(-79.91 -933.74)"/><path class="cls-5" d="M714.31,1836.73c-14.45,1.06-28.92,2.22-43.4,2.5a228.73,228.73,0,0,1-30.52-1.63c-13.95-1.6-28-3-41.61-6.33-18.92-4.72-37.86-10.05-55.92-17.29-16.57-6.66-32.53-15.32-47.76-24.73a385.72,385.72,0,0,1-43.21-31.31c-17.41-14.45-32.39-31.42-45.87-49.69-19.77-26.77-35.52-55.62-45.71-87.34-3.89-12.16-7.2-24.53-10-37a244.91,244.91,0,0,1-6.39-58.55c.35-19.07,1.85-38.13,3.42-57.16.55-6.79,2.74-13.46,4.18-20.18,1.82-8.1,3.37-16.27,5.52-24.28,7.61-28.46,18.17-55.86,30-82.82.4-.9.63-1.88.95-2.82.28-.49.62-1,.85-1.47,13.38-29.27,27.76-58,46.73-84.16,8.74-12,15.7-26,30.12-33-12,2-24.22,3.28-36,6.14-16.46,4-32.73,8.86-48.91,13.91-6.63,2.07-12.82,5.57-19.23,8.41a32.79,32.79,0,0,0-4.48,1.42A315.13,315.13,0,0,0,240.14,1345a305.1,305.1,0,0,0-38.54,67.31c-5.7,13.79-10.59,28-15,42.23a153.34,153.34,0,0,0-5.08,26.15c-2.22,17.95-4.46,35.93-5.49,54-.65,11.43.33,23,1.55,34.44,1.52,14.23,3.37,28.48,6.27,42.47a314.94,314.94,0,0,0,22.79,68.27c2.28,4.91,4.24,10,6.33,15l.87.87c6.85,11.16,13.44,22.49,20.61,33.45a336.3,336.3,0,0,0,60.83,69.51,364.88,364.88,0,0,0,191.63,86.54c21,2.75,42.12,4.05,63.3,2.69,13.77-.87,27.61-1.82,41.25-3.78,22.76-3.28,44.94-9.23,66.66-16.79,23.4-8.15,45.56-18.95,67.13-31,.63-.35,1-1.27,1.44-1.92-4.12.82-8.22,2.07-12.38,2.37" transform="translate(-79.91 -933.74)"/><path d="M1029.11,1454.42l-.22-6.08a208,208,0,0,0-2.56-24.44c-1-6.27-2.14-12.57-3.29-18.76a11.45,11.45,0,0,0-1.9-19.53,372.22,372.22,0,0,0,1.84-75.14,350.88,350.88,0,0,0-10-59.25,11.44,11.44,0,0,0-5.75-20.23,364.32,364.32,0,0,0-40.51-85.81,371.25,371.25,0,0,0-104.28-106.58,354.61,354.61,0,0,0-63.38-34.16c-9.09-3.75-19.15-7.31-31.72-11.18s-25.31-7.79-38.29-10.46c-8.42-1.74-17-3-25.45-4.13a11.46,11.46,0,0,0-20.1-3.55l-2.34-.9c-4.86-1.9-9.7-3.88-14.55-5.84-10.57-4.29-21.5-8.74-32.5-12.49a288.63,288.63,0,0,0-40.68-10.62A11.5,11.5,0,0,0,571,941.89c-7.39-.85-15-1.49-22.75-1.9a360.75,360.75,0,0,0-84.31,5A371.58,371.58,0,0,0,267.8,1047.58a362.09,362.09,0,0,0-66.05,90c-2.33,4.53-4.48,9.16-6.58,13.75a11.37,11.37,0,0,0-2.87-.41,11.53,11.53,0,0,0-8.18,19.66,354.57,354.57,0,0,0-67.73,102.1,350.32,350.32,0,0,0-21.84,67.13,366.6,366.6,0,0,0-8.2,87.06c.24,8.21.79,16.3,1.5,24.3a11.47,11.47,0,0,0,2.6,22.33c.82,5.49,1.74,10.92,2.8,16.26a372.2,372.2,0,0,0,43.05,114.16c12.82,22.11,25.77,40.24,39.57,55.42,8.33,9.15,17.25,18,25.88,26.56,1.47,1.47,2.93,2.94,4.4,4.4a11.52,11.52,0,0,0,9.4,18.2,11.68,11.68,0,0,0,4.27-.84c.14.25.3.51.46.76,4.57,7.6,9.32,15.43,14.26,23a339.33,339.33,0,0,0,61.12,69.84,365.81,365.81,0,0,0,92.85,58.93,11.3,11.3,0,0,0-.24,2.34,11.51,11.51,0,0,0,21.26,6.16,364.38,364.38,0,0,0,78.59,19.47c3.39.46,6.7.82,10,1.17,0,.06,0,.13,0,.19A11.51,11.51,0,0,0,521,1891c4.51.19,9,.33,13.31.33,5.94,0,11.73-.19,17.41-.55,14.67-.93,27.94-1.87,41.36-3.8,21.27-3.07,43.17-8.58,67-16.86,18.87-6.57,37.93-15.15,59.34-26.74a11.52,11.52,0,0,0,19.49-8.29c7.31-1.49,14.86-3,22.24-4.86A318.37,318.37,0,0,0,851,1792.34c14.34-8.78,29.46-18.35,42.93-30.11,10.23-8.93,20.82-18.17,31.06-27.48a63.77,63.77,0,0,0,5.79-6.32c1-1.14,1.92-2.3,2.91-3.4s2.25-2.37,3.39-3.55a79.91,79.91,0,0,0,6.25-7c3.64-4.76,7.17-9.59,10.7-14.42a11.49,11.49,0,0,0,13.58-18.47c11.65-16.26,20.59-34,28.1-49.59a266.9,266.9,0,0,0,14-36.42c5.07-16.27,10.26-33.76,13.83-51.24a317.87,317.87,0,0,0,5.83-46.85c.76-14.36.25-29-.24-43.1M843.61,1622.7a16.29,16.29,0,0,0-15.62,21.5q-10.64,4.87-21.2,8.8a16.43,16.43,0,0,0-30.82,8,13.62,13.62,0,0,0,.17,1.61c-5.14,1.28-10.27,2.48-15.43,3.47a321.16,321.16,0,0,1-56,6.09,16.38,16.38,0,0,0-14.4-14.59v-75.68a16.36,16.36,0,0,0,13.17-22.9,517.61,517.61,0,0,0,42.18-30.68,29.62,29.62,0,0,0,51.24-16l156.53,12.16a16.36,16.36,0,0,0,5.24,11.41c-1.52,2.17-3.06,4.32-4.62,6.43a329.86,329.86,0,0,1-51,54.33,318,318,0,0,1-46.25,32.78,16.46,16.46,0,0,0-13.19-6.68M727.34,1823.57a11.53,11.53,0,0,0-11.54,11.52c0,.27.06.52.08.78l-.46.06-4.27.32c-12.77.95-26,1.93-39,2.18-5.63.09-11.59-.1-17.87-.52-4-.27-8.06-.62-12.38-1.11l-4.61-.52c-12.19-1.36-24.79-2.79-36.72-5.76-15.72-3.93-36.26-9.42-55.62-17.19-14.72-5.9-30.23-13.95-47.42-24.55-2.58-1.6-5.18-3.31-7.77-5a16.42,16.42,0,0,0,2-7.65,16.1,16.1,0,0,0-1-5.33l183.78-88.15a16.39,16.39,0,0,0,30.17-6.66,327.86,327.86,0,0,0,56.72-6.16c5.19-1,10.35-2.2,15.51-3.5a16.43,16.43,0,0,0,8,9.26l-55.42,148.21a12.64,12.64,0,0,0-2.2-.21M226.93,1695.43a11.52,11.52,0,0,0-2-5.22L340.32,1597a16.39,16.39,0,0,0,11.54,4.73,17.21,17.21,0,0,0,2-.21c1.92,7.33,4,14.47,6.16,21.27,9.16,28.57,23,55.59,42.14,82.45a16.35,16.35,0,0,0-4.8,8.37Zm176.3-626.23c13-.86,30.2-1.65,47.72-.24,7.65.62,15,1.46,22.13,2.45,0,.17,0,.35,0,.52a16.44,16.44,0,0,0,31.9,5.65c3.15.79,6.27,1.6,9.35,2.49,18.33,5.3,39.73,12.31,59.58,24.2,3.07,1.84,6.17,3.62,9.28,5.41a16.23,16.23,0,0,0,1.35,14.86L489,1201.14a29.64,29.64,0,0,0-51.56,20,26.94,26.94,0,0,0,.14,2.79c-2.33.46-4.62.95-6.92,1.5-16.83,4.07-33.48,9.1-49,13.93a96.81,96.81,0,0,0-13,5.46,16.39,16.39,0,0,0-11.63-8l6.25-148.48A16.42,16.42,0,0,0,378.61,1072c8.39-1.31,16.57-2.23,24.61-2.75M576.64,955.47a10.84,10.84,0,0,0,7.25,1.16l67.48,192.62a16.13,16.13,0,0,0-3.59,2.39,295.09,295.09,0,0,0-31.21-25.77c-1.19-.84-2.41-1.63-3.59-2.44a16.43,16.43,0,0,0-28-17.08c-3.06-1.77-6.13-3.53-9.15-5.35-20.21-12.11-41.92-19.22-60.47-24.58-3.15-.9-6.33-1.74-9.54-2.55a15.35,15.35,0,0,0,.19-1.93,16.41,16.41,0,0,0-7-13.42Zm231.94,129.86c.9,1.27,1.8,2.53,2.69,3.85.63.9,1.25,1.88,1.88,2.82.81,1.22,1.63,2.44,2.45,3.7.7,1.08,1.39,2.2,2.09,3.31s1.52,2.36,2.26,3.59,1.5,2.48,2.25,3.74,1.38,2.25,2.06,3.4c1.58,2.68,3,5.41,4.53,8.12,1.09,2,2.23,3.91,3.28,5.89,1.27,2.41,2.41,4.86,3.61,7.3s2.37,4.67,3.48,7c.82,1.8,1.57,3.62,2.36,5.43,1.35,3.05,2.72,6.09,4,9.18.66,1.65,1.23,3.32,1.87,5,1.28,3.29,2.56,6.59,3.74,9.91a.23.23,0,0,0,0,.1,16.43,16.43,0,0,0,7.44,31.1,16,16,0,0,0,1.76-.17c.06.25.14.52.21.79.54,2.25,1,4.48,1.5,6.7.06.32.14.63.21.95.51,2.37.95,4.72,1.36,7,0,.16.06.32.09.49.43,2.41.81,4.78,1.16,7.16a1.37,1.37,0,0,0,0,.25c.35,2.45.66,4.89.93,7.3,1.49,13.57,3,29.6,3.26,45.65.19,13.61-1,28.33-3.85,45.87-.4,2.5-.84,5.06-1.3,7.69-1.38,7.74-2.91,15-4.64,22-.24,0-.47-.08-.73-.08a16.46,16.46,0,0,0-16.43,16l-67.62,8.21a16.2,16.2,0,0,0-18.92-12.33c-4.76-21.12-10.53-44-18.39-66.05-1.27-3.51-2.63-7-4-10.5a29.62,29.62,0,0,0-.08-53.76l58.31-150.61a16,16,0,0,0,4.12.59,16.3,16.3,0,0,0,8.69-2.55c.89,1.19,1.79,2.36,2.66,3.59.55.76,1.11,1.58,1.66,2.36M601.87,1656.68c-10.8-3.82-21.37-8.06-31.17-12.19a29.44,29.44,0,0,0,1.36-19.8c26.59-8.64,54-20.74,83.34-36.83,7.08-3.89,13.85-7.79,20.48-11.7a16.36,16.36,0,0,0,10.65,5.71v75.69a16.42,16.42,0,0,0-14.28,13.31c-4-.35-7.88-.7-11.7-1.09a253.22,253.22,0,0,1-58.69-13.11m86.58-107.57A16.37,16.37,0,0,0,673.78,1573c-6.55,3.85-13.23,7.71-20.21,11.54-29.14,16-56.35,28-82.74,36.57a29.66,29.66,0,0,0-52.35-4.31l-4-2.64c-11.62-7.79-23.62-15.83-35-24.34-4.24-3.18-8.39-6.59-12.49-10A16.4,16.4,0,0,0,443,1558.56a533.49,533.49,0,0,1-73-79.43c-.74-1-1.42-2.11-2.17-3.15a29.63,29.63,0,0,0-6.27-54.39c6.66-23.93,15.81-48.94,28.71-78.35a19.57,19.57,0,0,0,.74-2.11l.35-.9a13.83,13.83,0,0,0,.7-1.25c12.63-27.64,27-56.92,46.49-83.77,1.79-2.45,3.48-5,5.19-7.52,1.08-1.6,2.17-3.2,3.28-4.78a29.64,29.64,0,0,0,49.77-21.8c0-.16,0-.3,0-.46l7.2,0c21.05.11,42.82.22,64.09,1.77,44.69,3.26,83.91,10.64,119.9,22.57,1.95.65,3.78,1.27,5.54,1.88a29.68,29.68,0,0,0,36.17,40.19c1.35,3.45,2.71,6.9,3.94,10.37,7.84,21.94,13.6,44.8,18.33,65.86a16.42,16.42,0,0,0,5.45,31.45c2.11,22.54,3.69,58.88,4.56,84.35a29.63,29.63,0,0,0-18.65,46.17c-12.77,10.37-26.69,20.45-41.76,30.38a16.39,16.39,0,0,0-13.11-6.6m-275,151.66a16.28,16.28,0,0,0-8.17,2.25c-18.87-26.53-32.54-53.22-41.6-81.38-2.17-6.73-4.18-13.77-6.08-21a16.45,16.45,0,0,0-5.73-31.88c-.43,0-.82.1-1.25.13a233.48,233.48,0,0,1-3.24-42.09c.28-15.42,1.33-30.79,2.63-47.25a30.47,30.47,0,0,0,3.4.22,29.68,29.68,0,0,0,11-2.12c.87,1.25,1.69,2.55,2.6,3.77a536.24,536.24,0,0,0,73.24,79.78,16.36,16.36,0,0,0,6,25.72L415.8,1701a16.56,16.56,0,0,0-2.36-.24M291.08,1309.39a16.35,16.35,0,0,0-3.2-9.7q8.26-7.24,17-13.88A311.1,311.1,0,0,1,341,1262.37a16.4,16.4,0,0,0,29.27-14.07L373,1247a75.45,75.45,0,0,1,9.81-4c11.57-3.61,23.79-7.33,36.23-10.67q6.22-1.69,12.5-3.2c2.18-.54,4.4-1,6.62-1.44a29.66,29.66,0,0,0,6.14,12.44c-1.28,1.8-2.52,3.64-3.74,5.46-1.69,2.5-3.37,5-5.13,7.41-19.68,27.12-34.16,56.59-46.87,84.42a5.18,5.18,0,0,1-.47.81l-.74,1.8a15.6,15.6,0,0,1-.6,1.71c-13,29.66-22.24,54.89-29,79.07a28.85,28.85,0,0,0-17.24,2.56l-56.37-100.64a16.4,16.4,0,0,0,6.84-13.31m295.77-181.84a16.36,16.36,0,0,0,24-1c1.19.81,2.37,1.58,3.53,2.41a290.12,290.12,0,0,1,30.85,25.47,16.26,16.26,0,0,0-3.12,9.58,16.48,16.48,0,0,0,16.48,16.48,16.28,16.28,0,0,0,8.9-2.64,301.79,301.79,0,0,1,25.8,35.35c4.16,6.63,8,13.6,11.65,20.56a29.92,29.92,0,0,0-9.59,9.77c-1.93-.68-4-1.38-6.14-2.11-36.28-12-75.81-19.47-120.82-22.75-21.4-1.55-43.23-1.68-64.33-1.79l-7.55,0a29.49,29.49,0,0,0-5-12.76Zm222.7,328.31c-.71.92-10.1,11.3-25.29,27.89a29.5,29.5,0,0,0-16.68-5.13c-.62,0-1.2,0-1.8.08-.89-25.41-2.47-61.4-4.56-84A16.46,16.46,0,0,0,775,1378.44a.7.7,0,0,1,0-.08l67.42-8.18a16.44,16.44,0,0,0,7.3,10.54c-.22.55-.43,1.12-.65,1.66-6.81,16.72-15.29,33.13-22.7,47.2a201.76,201.76,0,0,1-12,19c-1.65,2.42-3.29,4.84-4.89,7.27M787.91,1080l-58.31,150.6a29.63,29.63,0,0,0-21.35,1.39c-3.69-7-7.55-14-11.76-20.77a306.59,306.59,0,0,0-26.09-35.73,16.37,16.37,0,0,0,3.21-18.09l109.93-80.71a16.59,16.59,0,0,0,4.37,3.31m-428.37,8.21-6.25,148.46a16.44,16.44,0,0,0-12,6.65L203.51,1165a11.63,11.63,0,0,0,.32-2.58,11.49,11.49,0,0,0-1.09-4.84c1.08-1,2.18-1.95,3.28-2.93,6.58-6,13.39-12.09,20.53-17.48,34.25-25.88,69-43.78,106.21-54.74,4.86-1.43,9.59-2.71,14.28-3.91a16.5,16.5,0,0,0,12.5,9.64M202,1168.55l137.3,78a16.21,16.21,0,0,0-.14,12.44,313.3,313.3,0,0,0-36.61,23.79c-5.94,4.48-11.71,9.21-17.32,14.14a16.4,16.4,0,0,0-23.44,22.78,330.69,330.69,0,0,0-21.54,25.69A309.26,309.26,0,0,0,208.75,1397a16.59,16.59,0,0,0-4.46-.9l-11.57-222.21a11.49,11.49,0,0,0,9.32-5.37M203.23,1429a16.46,16.46,0,0,0,9-30.3,303.62,303.62,0,0,1,31.14-51,327.42,327.42,0,0,1,21.23-25.34,16.31,16.31,0,0,0,10.05,3.51,16.5,16.5,0,0,0,6.33-1.27l56.35,100.59a29.63,29.63,0,0,0,9,53.64c-1.33,16.67-2.39,32.23-2.68,47.87a235.24,235.24,0,0,0,3.31,42.9,16.54,16.54,0,0,0-8.2,5.83l-141-81.47a16.71,16.71,0,0,0,1.36-6.54,16.45,16.45,0,0,0-12.38-15.91,118.48,118.48,0,0,1,3.43-15c3.1-10.1,6.25-19.33,9.47-28a17,17,0,0,0,3.62.43m-23.59,107.18c.25-4.35.59-8.72.95-13.07.55-6.41,1.25-12.82,2-19.19h0a16.41,16.41,0,0,0,13.19-6.68l141,81.47a16.35,16.35,0,0,0,1.2,15.32L222.18,1687.5a11.07,11.07,0,0,0-9.67-1.66c-.78-1.87-1.58-3.72-2.42-5.54a313.14,313.14,0,0,1-22.65-67.8c-3.15-15.23-5-30.58-6.24-42.25-1.36-12.79-2.15-23.49-1.55-34.1m239.84,165.78,30.38-114a15.89,15.89,0,0,0,2.88.29,16.39,16.39,0,0,0,12.13-5.4c4.07,3.43,8.18,6.81,12.43,10,11.4,8.58,23.46,16.65,35.11,24.46l4.24,2.83a29.64,29.64,0,0,0,52.3,27.76c9.92,4.18,20.67,8.5,31.66,12.38a257.4,257.4,0,0,0,59.58,13.3c3.86.4,7.85.78,11.87,1.11a16.39,16.39,0,0,0,.87,4.51l-183.77,88.14a16.38,16.38,0,0,0-25.77-2.61c-2.93-2.31-5.87-4.64-8.79-7.06a248.67,248.67,0,0,1-29.32-29,16.43,16.43,0,0,0-5.79-26.74m550.42-194.62a16.46,16.46,0,0,0-16.16,13.33l-156.52-12.17v-.19a29.56,29.56,0,0,0-9.94-22.13C800,1472.24,812,1459,812.71,1458q2.4-3.63,4.88-7.23a205.94,205.94,0,0,0,12.17-19.39c7.46-14.15,16-30.66,22.86-47.55.19-.48.36-1,.55-1.44a16.25,16.25,0,0,0,5.41,1,16.44,16.44,0,0,0,15.69-11.65l130.32,24.08c0,.06,0,.13,0,.19a11.53,11.53,0,0,0,9.24,11.3A338.72,338.72,0,0,1,976,1508.54a16.22,16.22,0,0,0-6.14-1.22m49.26-196.53a369,369,0,0,1-1.74,73.76,12.33,12.33,0,0,0-1.31-.13,11.52,11.52,0,0,0-10.81,7.61L875,1367.94c0-.36.11-.71.11-1.08a16.44,16.44,0,0,0-12-15.78c1.71-6.92,3.24-14.2,4.62-21.92,3.77-21.24,5.43-38.48,5.21-54.28-.22-16.21-1.79-32.35-3.29-46-.27-2.45-.59-4.92-.93-7.41,0-.1,0-.17,0-.25-.35-2.41-.73-4.83-1.17-7.27l-.09-.57c-.43-2.34-.87-4.7-1.38-7.07,0-.27-.11-.54-.17-.81-.49-2.3-1-4.62-1.57-7l-.19-.76a16.38,16.38,0,0,0,8.88-7.52l121.46,49.81a12.3,12.3,0,0,0-.22,2.31,11.54,11.54,0,0,0,11.52,11.54,11.33,11.33,0,0,0,3.89-.73,346.17,346.17,0,0,1,9.58,57.6M766.25,996.91c12.44,3.83,22.41,7.34,31.39,11.07a350.54,350.54,0,0,1,62.7,33.78,367.81,367.81,0,0,1,103.22,105.48,360.73,360.73,0,0,1,39.73,83.86,11.48,11.48,0,0,0-7.49,5.43L874.4,1186.72a16.29,16.29,0,0,0,.68-4.42,16.33,16.33,0,0,0-20.37-16l0-.11c-.08-.22-.14-.44-.22-.66-.93-2.64-2-5.26-3-7.87-.81-2.15-1.57-4.32-2.44-6.47-1.14-2.8-2.41-5.57-3.62-8.36-.93-2.14-1.82-4.27-2.79-6.39-1.06-2.28-2.23-4.51-3.36-6.77-1.25-2.57-2.47-5.14-3.8-7.68-1.2-2.29-2.52-4.53-3.77-6.79s-2.49-4.61-3.81-6.87c-.08-.14-.17-.27-.25-.41a1.47,1.47,0,0,0-.16-.27c-.92-1.58-1.85-3.09-2.77-4.62-.47-.77-.95-1.58-1.42-2.36-.95-1.52-1.88-3-2.82-4.45-.54-.85-1.09-1.76-1.63-2.6-.89-1.36-1.76-2.66-2.64-4l-1.77-2.66c-.82-1.22-1.66-2.37-2.49-3.55-.65-.92-1.3-1.87-1.95-2.75-.78-1.08-1.55-2.11-2.33-3.17-.13-.16-.25-.32-.36-.47a16.46,16.46,0,0,0-21.35-24.92c-1.08-1.12-2.12-2.28-3.21-3.4-9.32-9.58-19-18.79-28.87-27.45-10.84-9.51-22.76-17.81-34-25.34-5.29-3.55-10.8-6.79-16.29-10a11.4,11.4,0,0,0,.66-3.39c8,1.11,16.21,2.31,24.16,3.94,12.81,2.64,25.58,6.59,37.94,10.38m-101.05-25c4.86,2,9.72,3.94,14.59,5.86l2,.74a11.27,11.27,0,0,0-.63,3.59,11.49,11.49,0,0,0,20.5,7.19c5.43,3.13,10.86,6.33,16,9.8,11.18,7.47,23,15.67,33.67,25,10.94,9.59,21.57,19.86,31.8,30.6a16.11,16.11,0,0,0-1.82,18.82l-109.68,80.52a16.4,16.4,0,0,0-13.07-6.55,17.06,17.06,0,0,0-3.61.43L587.48,955.35A11.49,11.49,0,0,0,592.8,949a284.91,284.91,0,0,1,40.11,10.48c10.89,3.74,21.78,8.15,32.29,12.43M205.13,1139.28a358.7,358.7,0,0,1,65.36-89,367.77,367.77,0,0,1,194.1-101.53,357.5,357.5,0,0,1,83.43-5q11.49.62,22.41,1.87a11.44,11.44,0,0,0,3.15,7.55l-77.93,103.46a16.37,16.37,0,0,0-22,11c-7.23-1-14.67-1.85-22.41-2.47-8.82-.71-17.55-.87-25.71-.76s-16,.54-22.57,1c-8.12.52-16.38,1.46-24.84,2.77a16.45,16.45,0,0,0-32.2,6.71c-4.69,1.19-9.42,2.47-14.26,3.89-37.67,11.08-72.81,29.19-107.43,55.34-7.27,5.49-14.15,11.71-20.78,17.71l-2.9,2.57a12.22,12.22,0,0,0-1.92-1.58c2.07-4.53,4.19-9.1,6.47-13.53M98.25,1340.56a347.37,347.37,0,0,1,21.61-66.39A351,351,0,0,1,187,1173l.24-.28a12.24,12.24,0,0,0,1.65.65l11.62,222.9a16.4,16.4,0,0,0-4.46,31c-3.24,8.74-6.39,18-9.53,28.18a124.54,124.54,0,0,0-3.56,15.53c-.11,0-.22,0-.35,0a16.47,16.47,0,0,0-15.24,10.26l-64.49-17.95A10.25,10.25,0,0,0,103,1462a11.54,11.54,0,0,0-11.38-11.52c-.7-7.82-1.23-15.72-1.46-23.78a362.38,362.38,0,0,1,8.1-86.16m80.43,316.17c-13.63-15-26.4-32.88-39.08-54.75A368,368,0,0,1,97,1489c-1-5.18-1.93-10.45-2.74-15.78a11.57,11.57,0,0,0,7.65-6.28l64.49,18a16.73,16.73,0,0,0-.25,2.55,16.46,16.46,0,0,0,12.68,16c-1.23,10.8-2.33,21.69-2.94,32.53s.17,21.75,1.57,34.73c1.25,11.75,3.1,27.21,6.3,42.61a317.21,317.21,0,0,0,22.9,68.62c.84,1.82,1.63,3.66,2.41,5.51l-.13.11-4.21-4.15.06.11-.33-.33c-8.6-8.52-17.49-17.33-25.74-26.42m119.48,141.61a335.82,335.82,0,0,1-60.44-69c-4.91-7.5-9.62-15.32-14.18-22.87l-.49-.81a11.53,11.53,0,0,0,3.8-6.36L397,1717.4a16.46,16.46,0,0,0,13,16L399,1851a11.5,11.5,0,0,0-9,5.57,361.87,361.87,0,0,1-91.78-58.27m190.45,86A361.14,361.14,0,0,1,411,1865.13a12.08,12.08,0,0,0,.33-2.64,11.52,11.52,0,0,0-8.58-11.11l11-117.69a16.37,16.37,0,0,0,8.53-2.61,252.58,252.58,0,0,0,29.82,29.51c3,2.47,6,4.84,8.94,7.19a16.28,16.28,0,0,0-2.34,8.34,16.48,16.48,0,0,0,16.49,16.48,16.06,16.06,0,0,0,2.91-.3l26.36,86.89a11.54,11.54,0,0,0-5.68,6.38c-3.37-.36-6.76-.74-10.23-1.2m170.19-17.78c-23.55,8.2-45.22,13.66-66.26,16.68-13.28,1.92-26.45,2.85-41.06,3.77a279,279,0,0,1-30.53.24,11.52,11.52,0,0,0-11.32-9.32,11.08,11.08,0,0,0-1.46.14l-26.36-86.87a16.38,16.38,0,0,0,5.78-4.31c2.66,1.74,5.3,3.48,7.93,5.11,17.38,10.72,33.08,18.85,48,24.85,19.56,7.85,40.27,13.39,56.11,17.35,12.17,3,24.9,4.46,37.21,5.84l4.61.52c2.66.32,5.24.57,7.76.79l2.25.16c1.79.14,3.56.29,5.29.38.78,0,1.52.06,2.26.11l5,.17c.74,0,1.49,0,2.22,0s1.58,0,2.34,0c1.23,0,2.44,0,3.64,0,13.11-.25,26.37-1.23,39.21-2.18l4.26-.32c.35,0,.68-.1,1-.13.11.25.27.49.4.74-21.05,11.33-39.79,19.77-58.31,26.23m281.52-154.38a72.34,72.34,0,0,1-6,6.63c-1.17,1.2-2.34,2.41-3.47,3.66s-2,2.33-3,3.51a59.6,59.6,0,0,1-5.43,5.94c-10.21,9.28-20.78,18.5-31,27.43-13.25,11.57-28.22,21-42.41,29.71a314.28,314.28,0,0,1-88.83,37.51c-7.27,1.79-14.74,3.32-22,4.8a11.49,11.49,0,0,0-5.13-6.24l55.43-148.22a16.11,16.11,0,0,0,3.94.52A16.48,16.48,0,0,0,808.94,1661a16,16,0,0,0-.7-4.46q10.61-4,21.29-8.83a16.37,16.37,0,0,0,28.32-.33l91.46,39.1a11.33,11.33,0,0,0-.71,3.89,11.46,11.46,0,0,0,2.6,7.22c-3.59,4.91-7.19,9.81-10.87,14.64m85.22-214.88a314.37,314.37,0,0,1-5.76,46.28c-3.55,17.33-8.69,34.7-13.74,50.89a261.32,261.32,0,0,1-13.76,35.9c-7.46,15.48-16.37,33.18-27.92,49.23a11.37,11.37,0,0,0-13.17,3.5l-91.86-39.27a16.26,16.26,0,0,0,.74-4.65,16.64,16.64,0,0,0-1.35-6.54,323.18,323.18,0,0,0,46.77-33.15q3.63-3.09,7.15-6.25a333.39,333.39,0,0,0,44.48-48.71c1.58-2.12,3.12-4.29,4.65-6.47a16.23,16.23,0,0,0,8.1,2.22,16.46,16.46,0,0,0,9.48-29.93c2.07-3.64,4.08-7.31,6-11.07q7.5-14.44,13.76-29.81c1.92-4.72,3.7-9.53,5.43-14.4.25-.73.52-1.46.78-2.18q2.35-6.89,4.46-14c.32-1.06.62-2.14.93-3.21,1.31-4.61,2.56-9.29,3.72-14.09.32-1.23.59-2.52.89-3.77.79-3.45,1.57-6.95,2.28-10.49a13.24,13.24,0,0,0,1.82-.4c1.09,5.79,2.17,11.68,3.07,17.54a205.17,205.17,0,0,1,2.53,24l.21,6.09c.49,14,1,28.57.25,42.75" transform="translate(-79.91 -933.74)"/></svg>
\ No newline at end of file
diff --git a/static/assets/img/osm_small_logo.png b/static/assets/img/osm_small_logo.png
new file mode 100644 (file)
index 0000000..3bd7740
Binary files /dev/null and b/static/assets/img/osm_small_logo.png differ
diff --git a/static/assets/img/punto.png b/static/assets/img/punto.png
new file mode 100755 (executable)
index 0000000..2e748c2
Binary files /dev/null and b/static/assets/img/punto.png differ
diff --git a/static/assets/img/rdcl3dlogo0.png b/static/assets/img/rdcl3dlogo0.png
new file mode 100644 (file)
index 0000000..d3ad852
Binary files /dev/null and b/static/assets/img/rdcl3dlogo0.png differ
diff --git a/static/assets/img/rdcl3dlogo0.svg b/static/assets/img/rdcl3dlogo0.svg
new file mode 100644 (file)
index 0000000..9cb69f5
--- /dev/null
@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="210mm"
+   height="297mm"
+   viewBox="0 0 210 297"
+   version="1.1"
+   id="svg8"
+   inkscape:version="0.92.1 r"
+   sodipodi:docname="rdcl3dlogo0.svg">
+  <defs
+     id="defs2">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 148.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="210 : 148.5 : 1"
+       inkscape:persp3d-origin="105 : 99 : 1"
+       id="perspective3680" />
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath4908">
+      <path
+         d="M 0,595.28 H 841.89 V 0 H 0 Z"
+         id="path4906"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath4916">
+      <path
+         d="M 306.352,388.438 H 507.075 V 221.297 H 306.352 Z"
+         id="path4914"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+    <clipPath
+       clipPathUnits="userSpaceOnUse"
+       id="clipPath4932">
+      <path
+         d="M 316.115,390.875 H 536.503 V 227.491 H 316.115 Z"
+         id="path4930"
+         inkscape:connector-curvature="0" />
+    </clipPath>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.1767767"
+     inkscape:cx="739.64715"
+     inkscape:cy="-420.11255"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1918"
+     inkscape:window-height="1057"
+     inkscape:window-x="0"
+     inkscape:window-y="21"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:groupmode="layer"
+     id="layer3"
+     inkscape:label="mesh">
+    <path
+       sodipodi:type="star"
+       style="fill:#ffffff;fill-opacity:1;stroke:#bbbbbb;stroke-width:17.3841629;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4787"
+       sodipodi:sides="6"
+       sodipodi:cx="370.43555"
+       sodipodi:cy="366.59698"
+       sodipodi:r1="156.6655"
+       sodipodi:r2="67.910896"
+       sodipodi:arg1="0.80566566"
+       sodipodi:arg2="1.3292644"
+       inkscape:flatsided="true"
+       inkscape:rounded="0.08"
+       inkscape:randomized="0"
+       d="M 478.94697,479.59853 C 469.90684,488.27945 338.867,520.55992 326.82905,517.0714 314.79109,513.58288 221.31546,416.23929 218.31763,404.06986 215.3198,391.90042 252.884,262.27635 261.92413,253.59544 c 9.04012,-8.68092 140.07996,-40.96139 152.11792,-37.47287 12.03795,3.48852 105.51359,100.83211 108.51142,113.00154 2.99783,12.16944 -34.56638,141.79351 -43.6065,150.47442 z"
+       inkscape:transform-center-x="11.395173"
+       inkscape:transform-center-y="4.9400033"
+       transform="matrix(0.2835671,-1.1149783,1.1149783,0.2835671,-412.96205,855.48461)" />
+    <path
+       style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#bbbbbb;stroke-width:8.56500053;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M -54.970708,456.12241 56.534087,509.25559"
+       id="path4804"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#bbbbbb;stroke-width:8.56499958;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 148.44237,509.46287 257.59331,457.4675"
+       id="path4821"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#bbbbbb;stroke-width:8.56499958;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="m 99.938633,589.32954 -0.74837,135.45219"
+       id="path4823"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="internal">
+    <path
+       sodipodi:type="star"
+       style="fill:#ffffff;fill-opacity:1;stroke:#bbbbbb;stroke-width:17.38442993;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       id="path4787-9"
+       sodipodi:sides="6"
+       sodipodi:cx="370.43555"
+       sodipodi:cy="366.59698"
+       sodipodi:r1="156.6655"
+       sodipodi:r2="67.910896"
+       sodipodi:arg1="0.80566566"
+       sodipodi:arg2="1.3292644"
+       inkscape:flatsided="true"
+       inkscape:rounded="0.08"
+       inkscape:randomized="0"
+       d="M 478.94697,479.59853 C 469.90684,488.27945 338.867,520.55992 326.82905,517.0714 314.79109,513.58288 221.31546,416.23929 218.31763,404.06986 215.3198,391.90042 252.884,262.27635 261.92413,253.59544 c 9.04012,-8.68092 140.07996,-40.96139 152.11792,-37.47287 12.03795,3.48852 105.51359,100.83211 108.51142,113.00154 2.99783,12.16944 -34.56638,141.79351 -43.6065,150.47442 z"
+       inkscape:transform-center-x="6.2760914"
+       inkscape:transform-center-y="2.72079"
+       transform="matrix(0.15617987,-0.6140949,0.61409515,0.15617983,-183.01537,711.09896)" />
+  </g>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <flowRoot
+       xml:space="preserve"
+       id="flowRoot3759"
+       style="font-style:normal;font-weight:normal;font-size:122.66666412px;line-height:25px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       transform="scale(0.26458333)"><flowRegion
+         id="flowRegion3761"
+         style="font-size:122.66666412px"><rect
+           id="rect3763"
+           width="719.22864"
+           height="325.26913"
+           x="34.345188"
+           y="366.92557"
+           style="font-size:122.66666412px" /></flowRegion><flowPara
+         id="flowPara3765" /></flowRoot>    <g
+       transform="matrix(1.8009222,0,0,-1.7503182,-655.85326,1108.8177)"
+       id="g4902">
+      <g
+         id="g4904"
+         clip-path="url(#clipPath4908)">
+        <g
+           id="g4910">
+          <g
+             id="g4912" />
+          <g
+             id="g4924">
+            <g
+               clip-path="url(#clipPath4916)"
+               id="g4922"
+               style="opacity:0.60000598">
+              <g
+                 transform="translate(457.3057,235.4326)"
+                 id="g4920">
+                <path
+                   d="m 0,0 c 0,0 -67.64,-23.883 -88.679,-9.562 -17.311,11.784 21.144,30.998 54.706,48.909 66.833,35.666 92.133,92.846 68.499,82.501 -49.5,-21.667 -146.28,-33.667 -178.166,-20.667 -34.599,14.107 64.79,51.824 64.79,51.824 0,0 -92.79,-36.324 -62.341,-49.657 31.542,-13.813 128.717,-2.167 181.717,22.333 C 66.25,137.572 36.86,76.013 -29.973,40.347 -63.535,22.436 -101.99,5.222 -84.679,-6.562 -63.639,-20.883 0,0 0,0"
+                   style="fill:#00cccc;fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path4918"
+                   inkscape:connector-curvature="0" />
+              </g>
+            </g>
+          </g>
+        </g>
+        <g
+           id="g4926">
+          <g
+             id="g4928" />
+          <g
+             id="g4940">
+            <g
+               clip-path="url(#clipPath4932)"
+               id="g4938"
+               style="opacity:0.60000598">
+              <g
+                 transform="translate(457.3057,235.4326)"
+                 id="g4936">
+                <path
+                   d="m 0,0 c 0,0 -14.54,-5.354 -35.848,-5.152 -65.958,0.625 -37.39,18.992 19.496,49.218 62.81,33.374 131.466,136.558 73.946,105.683 -25.898,-13.901 -89.028,-53.093 -178.9,-39.401 -42.667,6.5 42.456,42.657 42.456,42.657 0,0 -99.941,-37.96 -46.742,-46.242 63.642,-9.907 139.443,7.67 175.353,28.049 36.385,20.649 -14.191,-62.434 -77,-95.807 C -84.126,8.778 -98.46,-7.945 -41.361,-7.941 -14.938,-7.939 0,0 0,0"
+                   style="fill:#0099ff;fill-opacity:1;fill-rule:nonzero;stroke:none"
+                   id="path4934"
+                   inkscape:connector-curvature="0" />
+              </g>
+            </g>
+          </g>
+        </g>
+        <g
+           id="g4942"
+           transform="translate(352.8857,367.7881)">
+          <path
+             d="m 0,0 c 0,-7.147 -5.794,-12.941 -12.941,-12.941 -7.148,0 -12.942,5.794 -12.942,12.941 0,7.147 5.794,12.942 12.942,12.942 C -5.794,12.942 0,7.147 0,0"
+             style="fill:#d1d3d4;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path4944"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="g4946"
+           transform="translate(514.8857,367.7886)">
+          <path
+             d="m 0,0 c 0,-7.146 -5.794,-12.941 -12.941,-12.941 -7.148,0 -12.943,5.795 -12.943,12.941 0,7.147 5.795,12.942 12.943,12.942 C -5.794,12.942 0,7.147 0,0"
+             style="fill:#d1d3d4;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path4948"
+             inkscape:connector-curvature="0" />
+        </g>
+        <g
+           id="g4950"
+           transform="translate(433.8857,227.4912)">
+          <path
+             d="m 0,0 c 0,-7.146 -5.794,-12.941 -12.941,-12.941 -7.148,0 -12.942,5.795 -12.942,12.941 0,7.146 5.794,12.941 12.942,12.941 C -5.794,12.941 0,7.146 0,0"
+             style="fill:#d1d3d4;fill-opacity:1;fill-rule:nonzero;stroke:none"
+             id="path4952"
+             inkscape:connector-curvature="0" />
+        </g>
+      </g>
+    </g>
+    <flowRoot
+       xml:space="preserve"
+       id="flowRoot76"
+       style="font-style:normal;font-weight:normal;font-size:40px;line-height:25px;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       transform="matrix(0.70626936,0,0,0.48683772,-1248.224,-229.64302)"><flowRegion
+         id="flowRegion78"><rect
+           id="rect80"
+           width="1107.8884"
+           height="103.54552"
+           x="1691.3994"
+           y="2053.0723" /></flowRegion><flowPara
+         id="flowPara82"
+         style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:120.32446289px;line-height:109.19898224px;font-family:Homizio;-inkscape-font-specification:Homizio;letter-spacing:12.25505257px">RDCL3D</flowPara></flowRoot>  </g>
+</svg>
diff --git a/static/assets/img/router.png b/static/assets/img/router.png
new file mode 100755 (executable)
index 0000000..29444e4
Binary files /dev/null and b/static/assets/img/router.png differ
diff --git a/static/assets/img/server.png b/static/assets/img/server.png
new file mode 100644 (file)
index 0000000..297299d
Binary files /dev/null and b/static/assets/img/server.png differ
diff --git a/static/assets/img/sf_logo.png b/static/assets/img/sf_logo.png
new file mode 100644 (file)
index 0000000..3577eaa
Binary files /dev/null and b/static/assets/img/sf_logo.png differ
diff --git a/static/assets/img/sf_logo_big.png b/static/assets/img/sf_logo_big.png
new file mode 100644 (file)
index 0000000..3a2554a
Binary files /dev/null and b/static/assets/img/sf_logo_big.png differ
diff --git a/static/assets/img/sf_small_logo.png b/static/assets/img/sf_small_logo.png
new file mode 100644 (file)
index 0000000..c540257
Binary files /dev/null and b/static/assets/img/sf_small_logo.png differ
diff --git a/static/assets/img/switch.png b/static/assets/img/switch.png
new file mode 100755 (executable)
index 0000000..86bff2c
Binary files /dev/null and b/static/assets/img/switch.png differ
diff --git a/static/assets/img/vl-80.png b/static/assets/img/vl-80.png
new file mode 100755 (executable)
index 0000000..fde16d0
Binary files /dev/null and b/static/assets/img/vl-80.png differ
diff --git a/static/assets/img/vnf-100.png b/static/assets/img/vnf-100.png
new file mode 100755 (executable)
index 0000000..bf893d8
Binary files /dev/null and b/static/assets/img/vnf-100.png differ
diff --git a/static/assets/img/vs.png b/static/assets/img/vs.png
new file mode 100755 (executable)
index 0000000..86bff2c
Binary files /dev/null and b/static/assets/img/vs.png differ
diff --git a/static/css/rdcl.css b/static/css/rdcl.css
new file mode 100644 (file)
index 0000000..1124aaf
--- /dev/null
@@ -0,0 +1,44 @@
+.select-container-rdcl{
+
+}
+
+.select-container-rdcl-loading > .fa{
+    visibility: visible !important;
+}
+
+.select-container-rdcl-loaded > .fa{
+    visibility: hidden !important;
+}
+
+.select-container-rdcl-loading > .select-rdcl{
+    pointer-events: none !important;
+}
+
+.select-container-rdcl-loaded > .select-rdcl{
+    pointer-events: all !important;
+}
+
+.shellIframe {
+    width: 100%;
+    height: 450px;
+}
+
+/* layout.css Style */
+.upload-drop-zone {
+  height: 200px;
+  border-width: 2px;
+  margin-bottom: 20px;
+}
+
+/* skin.css Style*/
+.upload-drop-zone {
+  color: #ccc;
+  border-style: dashed;
+  border-color: #ccc;
+  line-height: 200px;
+  text-align: center
+}
+.upload-drop-zone.drop {
+  color: #222;
+  border-color: #222;
+}
\ No newline at end of file
diff --git a/static/src/adminlte_session_storage.js b/static/src/adminlte_session_storage.js
new file mode 100644 (file)
index 0000000..d18cbbe
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+
+function setCookie(cname, cvalue, exdays) {
+    var d = new Date();
+    d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
+    var expires = "expires=" + d.toUTCString();
+    document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
+}
+
+function getCookie(cname) {
+    var name = cname + "=";
+    var ca = document.cookie.split(';');
+    for (var i = 0; i < ca.length; i++) {
+        var c = ca[i];
+        while (c.charAt(0) == ' ') {
+            c = c.substring(1);
+        }
+        if (c.indexOf(name) == 0) {
+            return c.substring(name.length, c.length);
+        }
+    }
+    return "";
+}
+
+
+$(document).ready(function () {
+
+    //event section
+
+    // save if sidebar is collapsed or not
+    $('.sidebar-toggle').click(function (event) {
+        event.preventDefault();
+        var cookie = getCookie("collapsed_sidebar");
+        if (cookie != "") {
+            setCookie("collapsed_sidebar", "")
+        } else {
+            setCookie("collapsed_sidebar", "1")
+        }
+    });
+
+});
\ No newline at end of file
diff --git a/static/src/instancehandler/instance_list.js b/static/src/instancehandler/instance_list.js
new file mode 100644 (file)
index 0000000..161cc51
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+
+function performAction(url) {
+    $("#formActionNS").attr("action", url);
+    $('#modal_instance_new_action').modal('show');
+}
+
+function deleteNs(url) {
+    bootbox.confirm("Are you sure want to delete?", function (result) {
+        if (result) {
+            location.href = url
+        }
+    })
+}
+
+var addFormGroup = function (event) {
+    event.preventDefault();
+
+    var $formGroup = $(this).closest('.form-group');
+    var $formGroupClone = $formGroup.clone();
+
+    $(this)
+        .toggleClass('btn-success btn-add btn-danger btn-remove')
+        .html('–');
+
+    $formGroupClone.find('input').val('');
+    $formGroupClone.insertAfter($formGroup);
+
+};
+
+var removeFormGroup = function (event) {
+    event.preventDefault();
+    var $formGroup = $(this).closest('.form-group');
+    $formGroup.remove();
+};
+
+function showInstanceDetails(url_info) {
+    var dialog = bootbox.dialog({
+        message: '<div class="text-center"><i class="fa fa-spin fa-spinner"></i> Loading...</div>',
+        closeButton: true
+    });
+    $.ajax({
+        url: url_info,
+        type: 'GET',
+        dataType: "json",
+        contentType: "application/json;charset=utf-8",
+        success: function (result) {
+            editorJSON.setValue(JSON.stringify(result, null, "\t"));
+            editorJSON.setOption("autoRefresh", true);
+            dialog.modal('hide');
+            $('#modal_show_instance').modal('show');
+        },
+        error: function (result) {
+            dialog.modal('hide');
+            bootbox.alert("An error occurred while retrieving the information for the NS");
+        }
+    });
+}
+
+var editorJSON;
+
+$(document).ready(function () {
+    var json_editor_settings = {
+        mode: "javascript",
+        showCursorWhenSelecting: true,
+        autofocus: true,
+        lineNumbers: true,
+        lineWrapping: true,
+        foldGutter: true,
+        gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
+        autoCloseBrackets: true,
+        matchBrackets: true,
+        extraKeys: {
+            "F11": function (cm) {
+                cm.setOption("fullScreen", !cm.getOption("fullScreen"));
+            },
+            "Esc": function (cm) {
+                if (cm.getOption("fullScreen")) cm.setOption("fullScreen", false);
+            },
+            "Ctrl-Q": function (cm) {
+                cm.foldCode(cm.getCursor());
+            }
+        },
+        theme: "neat",
+        keyMap: "sublime"
+    };
+    var myJsonTextArea = document.getElementById("instance_view_json");
+    editorJSON = CodeMirror(function (elt) {
+        myJsonTextArea.parentNode.replaceChild(elt, myJsonTextArea);
+    }, json_editor_settings);
+
+
+    $(document).on('click', '.btn-add', addFormGroup);
+    $(document).on('click', '.btn-remove', removeFormGroup);
+
+    $("#formActionNS").submit(function (event) {
+        event.preventDefault(); //prevent default action
+        var post_url = $(this).attr("action"); //get form action url
+        var request_method = $(this).attr("method"); //get form GET/POST method
+        var form_data = new FormData(this); //Encode form elements for submission
+        console.log(post_url);
+        $.ajax({
+            url: post_url,
+            type: request_method,
+            data: form_data,
+            headers: {
+                "Accept": 'application/json'
+            },
+            contentType: false,
+            processData: false
+        }).done(function (response,textStatus, jqXHR) {
+            $('#modal_instance_new_action').modal('hide');
+        }).fail(function(result){
+            var data  = result.responseJSON;
+            var title = "Error " + (data.code ? data.code: 'unknown');
+                var message = data.detail ? data.detail: 'No detail available.';
+                bootbox.alert({
+                    title: title,
+                    message: message
+                });
+        });
+    });
+
+});
\ No newline at end of file
diff --git a/static/src/projecthandler/descriptorslist.js b/static/src/projecthandler/descriptorslist.js
new file mode 100644 (file)
index 0000000..4a5975a
--- /dev/null
@@ -0,0 +1,69 @@
+
+function startFromAgent(start) {
+
+
+    if (start == 'exist'){
+        $('#div_new_agent').hide();
+        $('#div_available_agent').show();
+    }
+    else if (start == 'new'){
+        $('#div_available_agent').hide();
+        $('#div_new_agent').show();
+    }
+
+    $('.required').prop('required', function(){
+        return  $(this).is(':visible');
+    });
+
+}
+/*
+function InvalidGitUrl(textbox) {
+    console.log('InvalidGitUrl', textbox,textbox.value == '' ,textbox.validity.typeMismatch)
+    if (textbox.value === '') {
+        textbox.setCustomValidity('Required git URL');
+    }
+    else if (textbox.validity.typeMismatch){
+        textbox.setCustomValidity('please enter a valid git URL');
+
+    }
+    else {
+        textbox.setCustomValidity('please enter a valid git URL');
+    }
+    return true;
+}
+*/
+function startFromRepo(start) {
+
+
+    if (start == 'exist'){
+        $('#div_new_repo').hide();
+        $('#div_available_repo').show();
+    }
+    else if (start == 'new'){
+        $('#div_available_repo').hide();
+        $('#div_new_repo').show();
+    }
+
+    $('.required').prop('required', function(){
+        return  $(this).is(':visible');
+    });
+
+}
+
+$(document).ready(function () {
+    $("#startButtonsSelect :input").change(function () {
+        console.log("select")
+        startFromAgent(this.value);
+    });
+    $("#push_start_buttons_select :input").change(function () {
+        console.log("select")
+        startFromRepo(this.value);
+    });
+
+    // Bind events
+    $("form").submit(function(e) {
+        console.log("on submit form")
+        $("#start_new_deployment").button('loading');
+    });
+
+});
\ No newline at end of file
diff --git a/static/src/projecthandler/new_project.js b/static/src/projecthandler/new_project.js
new file mode 100644 (file)
index 0000000..9ca4c9e
--- /dev/null
@@ -0,0 +1,84 @@
+/**
+ *      New Project page
+ **/
+function handleTypeChoose(type) {
+    resetStartFromInputs()
+    $('#projectType').val(type);
+    $('#startGroup').show();
+    $('input[id="scratch"]').prop('checked', true);
+    $('#startButtonsSelect label').removeClass("active");
+    $('#s-scratch').addClass("active");
+    $('#createButton').prop('disabled', false);
+    //$('#projectName').val('New_'+type+'_project')
+}
+
+function startFromChoose(start) {
+    resetStartFromInputs()
+    //resetSelectors();
+    var type = $('#select_type').val();
+
+    if (start == 'files')
+        $('#div-file-upload-' + type).show();
+    else if (start == 'example')
+        //document.getElementById['#div-example-' + type].style.display = "block";
+       $('#div-example-' + type).css("display", "inline");
+
+}
+
+function resetStartFromInputs() {
+    $('div[class="start-selector"]').hide();
+    $('input[type="file"]').val('');
+    $('select[class="example-selector"]').val(null).trigger("change");
+}
+
+$(document).ready(function () {
+
+    // init selector
+    $(".start-selector").css("display", "inline");
+    $('#select_type').select2({
+        placeholder: {
+            id: '-1',
+            text: 'Select an option'
+        },
+        data: data_type_selector
+    });
+
+
+    $('#select_type').on("select2:select", function (evt) {
+        if (evt) {
+            var args = evt.params;
+            handleTypeChoose(args.data.value)
+        }
+    });
+
+    if (type_example_files) {
+        for (var key in type_example_files) {
+            $('select[id="example-' + key + '"]').select2({
+                placeholder: {
+                    id: '-1',
+                    text: 'Select an option'
+                },
+                data: type_example_files[key]
+            });
+        }
+
+
+    }
+
+
+
+    $("#startButtonsSelect :input").change(function () {
+        startFromChoose(this.value);
+    });
+
+
+
+    $("body").bind("ajaxSend", function (elm, xhr, s) {
+        if (s.type == "POST") {
+            xhr.setRequestHeader('csrftoken', $('#csrfmiddlewaretoken').val());
+        }
+    });
+
+    $(".start-selector").css("display", "none");
+
+});
\ No newline at end of file
diff --git a/static/src/projecthandler/osm/controller.js b/static/src/projecthandler/osm/controller.js
new file mode 100644 (file)
index 0000000..e39e332
--- /dev/null
@@ -0,0 +1,107 @@
+if (typeof dreamer === 'undefined') {
+    var dreamer = {};
+}
+var level = {}
+
+dreamer.OsmController = (function(global) {
+    'use strict';
+
+    var DEBUG = true;
+
+    OsmController.prototype.constructor = OsmController;
+
+    /**
+     * Constructor
+     */
+    function OsmController() {
+
+
+    }
+
+
+    OsmController.prototype.addNode = function(graph_editor, node, success, error) {
+        log('addNode');
+        var data_to_send = {
+            'group_id': node.info.group[0],
+            'element_id': node.id,
+            'element_type': node.info.type,
+            'element_desc_id': node.info.desc_id,
+            'x': node.x,
+            'y': node.y
+         };
+        new dreamer.GraphRequests().addNode(data_to_send, null, function() {
+            if (success)
+                success();
+        },error);
+    };
+
+    OsmController.prototype.addLink = function(graph_editor, link, success, error) {
+        log('addLink');
+        var data_to_send = {
+            'desc_id': link.desc_id,
+            'source': link.source.id,
+            'source_type': link.source.info.type,
+            'target': link.target.id,
+            'target_type': link.target.info.type,
+            'view': link.view,
+            'group': link.group
+        };
+        new dreamer.GraphRequests().addLink(link, null, function() {
+            graph_editor._deselectAllNodes();
+
+            if (typeof old_link !== 'undefined' && old_link.length > 0 && old_link[0].index !== 'undefined') {
+                graph_editor.parent.removeLink.call(graph_editor, old_link[0].index);
+            }
+            if (success) {
+                success();
+            }
+        },error);
+    };
+
+    OsmController.prototype.removeNode = function(graph_editor, node, success, error) {
+        log('removeNode');
+        var data_to_send = {
+            'group_id': node.info.group[0],
+            'element_id': node.id,
+            'element_type': node.info.type,
+            'element_desc_id': node.info.desc_id,
+            };
+        new dreamer.GraphRequests().removeNode(data_to_send, null, function() {
+            if (success) {
+                success();
+            }
+        },error);
+    };
+
+    OsmController.prototype.removeLink = function(graph_editor, link, success, error) {
+        log('removeLink');
+        var data_to_send = {
+            'desc_id': link.desc_id,
+            'source': link.source.id,
+            'source_type': link.source.info.type,
+            'target': link.target.id,
+            'target_type': link.target.info.type,
+            'view': link.view,
+            'group': link.group
+        };
+        new dreamer.GraphRequests().removeLink(data_to_send, function() {
+            if (success) {
+                success();
+            }
+        },error);
+    };
+
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::OsmController::", text);
+    }
+
+    return OsmController;
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.OsmController;
+}
\ No newline at end of file
diff --git a/static/src/projecthandler/osm/gui_properties.js b/static/src/projecthandler/osm/gui_properties.js
new file mode 100644 (file)
index 0000000..1af3ef4
--- /dev/null
@@ -0,0 +1,128 @@
+//***STEFANO
+var example_gui_properties = {
+  "v1" : {
+    "default": {
+      "shape": "cross",
+      "color": "#42f44e",
+      "label_color": "black",
+      "size": 15
+    },
+    "nodes": {
+      "ns_vl": {
+        "image" : "vl-80.png",
+        // "shape": "triangle",
+        "color": "#196B90",
+        "size": 20,
+        "name": "VL"
+      },
+      "ns_cp": {
+        "image" : "cp-80.png",
+        // "shape": "circle",
+        "color": "#F27220",
+        "size": 20,
+        "name": "CP"
+      },
+      "vnf": {
+        "image": "vnf-100.png",
+        // "shape": "square",
+        "color": "#54A698",
+        "size": 35,
+        "name": "VNF"
+      },
+      "vnf_vl": {
+        "shape": "triangle",
+        //"color": "#5FC9DB",
+        "color": "#196B90",
+        "size": 11,
+        "name": "IntVL"
+      },
+      "vnf_ext_cp": {
+        "shape": "circle",
+        //"#00CC66",
+        "color": "#F27220",
+        "size": 15,
+        "name": "ExtCP"
+      },
+      "vnf_vdu_cp": {
+        "shape": "circle",
+        //"color": "#E74C35",
+        "color": "#fce0cf",
+        "size": 15,
+        "name": "VduCP"
+      },
+      "vnf_vdu": {
+        "shape": "square",
+        //"color": "#50A7CC",
+        "color": "#54A698",
+        "size": 18,
+        "name": "VDU"
+      }
+    },
+    "graphs": null
+
+  },
+  "v0" : {
+    "default": {
+      "shape": "cross",
+      "color": "#42f44e",
+      "label_color": "black",
+      "size": 15
+    },
+    "nodes": {
+      "pippo": {
+        "image": "image.png",
+        "size": 25
+      },
+      "ns_vl": {
+        "shape": "triangle",
+        "color": "#196B90",
+        "size": 11,
+        "name": "VL"
+      },
+      "ns_cp": {
+        "shape": "circle",
+        "color": "#F27220",
+        "size": 15,
+        "name": "CP"
+      },
+      "vnf": {
+        "shape": "square",
+        "color": "#54A698",
+        "image": "router.png",
+        "size": 40,
+        "name": "VNF"
+      },
+      "vnf_vl": {
+        "shape": "triangle",
+        //"color": "#5FC9DB",
+        "color": "#196B90",
+        "size": 11,
+        "name": "IntVL"
+      },
+      "vnf_ext_cp": {
+        "shape": "circle",
+        //"#00CC66",
+        "color": "#F27220",
+        "size": 15,
+        "name": "ExtCP"
+      },
+      "vnf_vdu_cp": {
+        "shape": "circle",
+        //"color": "#E74C35",
+        "color": "#fce0cf",
+        "size": 15,
+        "name": "VduCP"
+      },
+      "vnf_vdu": {
+        "shape": "square",
+        //"color": "#50A7CC",
+        "color": "#54A698",
+        "size": 18,
+        "name": "VDU"
+      }
+    },
+    "graphs": null
+
+  },
+
+}
\ No newline at end of file
diff --git a/static/src/projecthandler/osm/project_graph.js b/static/src/projecthandler/osm/project_graph.js
new file mode 100644 (file)
index 0000000..072129f
--- /dev/null
@@ -0,0 +1,354 @@
+//GraphEditor instance
+var graph_editor = new dreamer.ModelGraphEditor();
+var selected_vnffgId = null;
+var show_all = null;
+
+// Enable Drop Action on the Graph
+initDropOnGraph();
+
+
+
+$(document).ready(function() {
+    var descriptor_type = getUrlParameter('type') == 'ns' || getUrlParameter('type') == 'nsd' ? 'ns' : 'vnf'
+    var allowed_types = descriptor_type == 'ns' ? ['vnf', 'ns_cp', 'ns_vl'] : ['vnf_vl', 'vnf_ext_cp', 'vnf_vdu_cp', 'vnf_vdu'];
+    var params = {
+        node: {
+            type: allowed_types,
+            group: [getUrlParameter('id')]
+        },
+        link: {
+            group: [getUrlParameter('id')],
+            view: [descriptor_type]
+        }
+    }
+
+    graph_editor.addListener("refresh_graph_parameters", refreshGraphParameters);
+
+
+    // graph_editor initialization
+    graph_editor.init({
+        width: $('#graph_ed_container').width(),
+        height: $('#graph_ed_container').height(),
+        gui_properties: example_gui_properties,
+        filter_base: params,
+        behaviorsOnEvents:{
+            viewBased: false,
+            behaviors: buildBehaviorsOnEvents()
+        }
+    });
+
+    // this will filter in the different views, excluding the node types that are not listed in params
+    graph_editor.handleFiltersParams(params);
+    graph_editor.addListener("filters_changed", changeFilter);
+    graph_editor.addListener("edit_descriptor", openEditorEvent);
+
+});
+
+
+function initDropOnGraph() {
+
+    var dropZone = document.getElementById('graph_ed_container');
+    dropZone.ondrop = function(e) {
+        var group = graph_editor.getCurrentGroup()
+        e.preventDefault();
+        var nodetype = e.dataTransfer.getData("text/plain");
+        if (nodetype) {
+            var type_name = graph_editor.getTypeProperty()[nodetype].name;
+            if (nodetype == 'vnf') {
+                new dreamer.GraphRequests().getUnusedVnf(group, function(vnfs) {
+                    $('#div_chose_id').hide();
+                    $('#div_chose_vnf').show();
+                    $('#input_choose_node_id').val(nodetype + "_" + generateUID());
+                    $('#selection_chooser_vnf').empty();
+                    $('#selection_chooser_vnf').append('<option >None</option>');
+                    $('#modal_chooser_title_add_node').text('Add ' + type_name);
+                    for (var i in vnfs) {
+                        $('#selection_chooser_vnf').append('<option id="' + vnfs[i] + '">' + vnfs[i] + '</option>');
+                    }
+                    $('#save_choose_node_id').off('click').on('click', function() {
+                        var choice = $("#selection_chooser_vnf option:selected").text();
+                        var name = $('#input_choose_node_id').val();
+                        if (choice == 'None') {
+                            var node_information = {
+                                'id': name,
+                                'info': {
+                                    'type': nodetype,
+                                    'group': [group]
+                                },
+                                'x': e.layerX,
+                                'y': e.layerY
+                            }
+                            graph_editor.addNode(node_information, function() {
+                                $('#modal_choose_node_id').modal('hide');
+                            }, function(error){
+                        showAlert(error)
+                    });
+                        } else {
+                            var node_information = {
+                                'existing_element': true,
+                                'id': choice,
+                                'info': {
+                                    'type': nodetype,
+                                    'group': [group]
+                                },
+                                'x': e.layerX,
+                                'y': e.layerY
+                            }
+                            graph_editor.addNode(node_information, function() {
+                                $('#modal_choose_node_id').modal('hide');
+                            }, function(error){
+                        showAlert(error)
+                    });
+                        }
+
+                    });
+
+                    $('#modal_choose_node_id').modal('show');
+                });
+
+            } else {
+                $('#div_chose_id').show();
+                $('#div_chose_vnf').hide();
+                $('#input_choose_node_id').val(nodetype + "_" + generateUID());
+                $('#modal_chooser_title_add_node').text('Add ' + type_name);
+                $('#save_choose_node_id').off('click').on('click', function() {
+                    var name = $('#input_choose_node_id').val();
+                    var node_information = {
+                        'id': name,
+                        'info': {
+                            'type': nodetype,
+                            'group': [group]
+                        },
+                        'x': e.layerX,
+                        'y': e.layerY
+                    }
+                    graph_editor.addNode(node_information, function() {
+                        $('#modal_choose_node_id').modal('hide');
+                    }, function(error){
+                        showAlert(error)
+                    });
+                });
+                $('#modal_choose_node_id').modal('show');
+
+            }
+        }
+
+    }
+
+    dropZone.ondragover = function(ev) {
+        console.log("ondragover");
+        return false;
+    }
+
+    dropZone.ondragleave = function() {
+        console.log("ondragleave");
+        return false;
+    }
+}
+
+function handleForce(el) {
+    if (el.id == "topology_play") {
+        $("#topology_pause").removeClass('active');
+        $("#topology_play").addClass('active');
+    } else {
+        $("#topology_pause").addClass('active');
+        $("#topology_play").removeClass('active');
+    }
+
+    graph_editor.handleForce((el.id == "topology_play") ? true : false);
+
+}
+
+function changeFilter(e, c) {
+    var type_property = graph_editor.getTypeProperty();
+    if (c.link.view == 'ns') {
+        $("#title_header").text("NS Graph Editor")
+        $("#vnffg_options").prop("disabled", false);
+        graph_editor.refreshGraphParameters();
+    } else {
+
+        $("#title_header").text("VNF Graph Editor");
+        $("#vnffg_box").hide();
+        $("#vnffg_options").prop("disabled", true);
+    }
+
+   new dreamer.GraphRequests().getAvailableNodes({layer: c.link.view[0]}, buildPalette, showAlert);
+   updateBredCrumb(c);
+}
+
+var filters = function(e, params) {
+    graph_editor.handleFiltersParams(params);
+    $('#' + e).nextAll('li').remove();
+}
+
+function updateBredCrumb(filter_parameters){
+     var newLi = $("<li id=" + JSON.stringify(graph_editor.getCurrentGroup()) + "><a href='javascript:filters(" + JSON.stringify(graph_editor.getCurrentGroup()) + "," + JSON.stringify(filter_parameters) + ")'>" + graph_editor.getCurrentGroup() + "</a></li>");
+     $('#breadcrumb').append(newLi);
+}
+
+
+function openEditor(project_id) {
+    window.location.href = '/projects/' + project_id + '/descriptors/' + graph_editor.getCurrentView() + 'd/' + graph_editor.getCurrentGroup();
+}
+
+
+function showChooserModal(title, chooses, callback) {
+    console.log('showchooser')
+    $('#selection_chooser').empty();
+    for (var i in chooses) {
+        $('#selection_chooser').append('<option id="' + chooses[i].id + '">' + chooses[i].id + '</option>');
+    }
+    $('#modal_chooser_title').text(title)
+    var self = this;
+    $('#save_chooser').off('click').on('click', function() {
+        var choice = $("#selection_chooser option:selected").text();
+        callback(choice);
+
+    });
+    $('#modal_create_link_chooser').modal('show');
+
+}
+
+function refreshGraphParameters(e, graphParameters) {
+
+    var self = $(this);
+    if (graphParameters == null) return;
+    var vnffgIds = graphParameters.vnffgIds;
+    if (vnffgIds == null) return;
+
+    $("#selection_vnffg").empty();
+    $("#selection_vnffg").append('<option value="Global">Global</option>')
+    for (var i in vnffgIds) {
+        var vnffgId = vnffgIds[i]
+        var child = $('<option value="' + vnffgId + '">' + vnffgId + '</option>');
+        $("#selection_vnffg").append(child)
+    }
+}
+
+function changeVnffg(e) {
+    var vnffgId = e.value;
+    selected_vnffgId = vnffgId;
+    show_all_change();
+}
+
+function newVnffg() {
+    var group = graph_editor.getCurrentGroup()
+    $('#div_chose_id').show();
+    $('#div_chose_vnf').hide();
+    $('#input_choose_node_id').val("vnffg_" + generateUID());
+    $('#modal_chooser_title_add_node').text('Add VNFFG');
+    $('#save_choose_node_id').off('click').on('click', function() {
+        var name = $('#input_choose_node_id').val();
+        var node_information = {
+            'element_id': name,
+            'element_type': "vnffg",
+            'group_id': group,
+        }
+        console.log(JSON.stringify(node_information))
+        new dreamer.GraphRequests().addVnffg(node_information, function(result) {
+
+            $('#modal_choose_node_id').modal('hide');
+            graph_editor.d3_graph.graph_parameters.vnffgIds.push(node_information.id)
+            refreshGraphParameters(null, graph_editor.d3_graph.graph_parameters)
+        });
+
+
+
+    });
+    $('#modal_choose_node_id').modal('show');
+}
+
+function show_all_change(e) {
+    if (!selected_vnffgId) return;
+    var vnffgId = selected_vnffgId;
+    if (e) show_all = e.checked;
+    if (show_all) {
+        handleVnffgParameter("Global", "invisible");
+        handleVnffgParameter(vnffgId, "matted");
+    } else {
+        handleVnffgParameter("Global", "matted");
+        handleVnffgParameter(vnffgId, "invisible");
+    }
+}
+
+function clickVnffg() {
+    if ($("#vnffg_box").is(':visible'))
+        $("#vnffg_box").hide();
+    else
+        $("#vnffg_box").show();
+
+}
+
+function handleVnffgParameter(vnffgId, class_name) {
+
+    if (vnffgId != "Global") {
+        selected_vnffgId = vnffgId;
+        graph_editor.setNodeClass(class_name, function(d) {
+            var result = false;
+            if (d.info.group.indexOf(vnffgId) < 0) {
+                result = true;
+            }
+            console.log(result);
+            return result;
+        });
+
+        graph_editor.setLinkClass(class_name, function(d) {
+            var result = false;
+            if (d.group.indexOf(vnffgId) < 0) {
+                result = true;
+            }
+            console.log(result);
+            return result;
+        });
+
+    } else {
+        selected_vnffgId = null;
+        graph_editor.setNodeClass(class_name, function(d) {
+            var result = false;
+            return result;
+        });
+
+        graph_editor.setLinkClass(class_name, function(d) {
+            var result = false;
+            return result;
+        });
+    }
+}
+
+function buildBehaviorsOnEvents(){
+    var contextmenuNodesAction = [
+        {
+                title: 'Show graph',
+                action: function (elm, c_node, i) {
+                    if (c_node.info.type != undefined) {
+                        var current_layer_nodes = Object.keys(graph_editor.model.layer[graph_editor.getCurrentView()].nodes);
+                        if (current_layer_nodes.indexOf(c_node.info.type) >= 0) {
+                            if (graph_editor.model.layer[graph_editor.getCurrentView()].nodes[c_node.info.type].expands) {
+                                var new_layer = graph_editor.model.layer[graph_editor.getCurrentView()].nodes[c_node.info.type].expands;
+                                graph_editor.handleFiltersParams({
+                                    node: {
+                                        type: Object.keys(graph_editor.model.layer[new_layer].nodes),
+                                        group: [c_node.id]
+                                    },
+                                    link: {
+                                        group: [c_node.id],
+                                        view: [new_layer]
+                                    }
+                                });
+
+                            }
+                            else{
+                                showAlert('This is not an explorable node.')
+                            }
+                        }
+                    }
+                },
+                edit_mode: false
+        }];
+        var behavioursOnEvents = {
+            'nodes': contextmenuNodesAction,
+
+        };
+
+    return behavioursOnEvents;
+}
\ No newline at end of file
diff --git a/static/src/utils.js b/static/src/utils.js
new file mode 100644 (file)
index 0000000..6d5e820
--- /dev/null
@@ -0,0 +1,153 @@
+function generateUID() {
+    return ("0000" + (Math.random() * Math.pow(36, 4) << 0).toString(36)).slice(-4)
+}
+
+function openProject(pId) {
+    window.location.href = '/projects/' + pId;
+}
+
+
+function openDescriptorView(project_id, descriptor_type, descriptor_id) {
+    console.log("openDescriptorView", project_id, descriptor_type, descriptor_id);
+    window.location.href = '/projects/' + project_id + '/descriptors/' + descriptor_type + '/' + descriptor_id;
+
+}
+
+
+function openEditorEvent(e, id) {
+    openEditor(id);
+}
+
+function nodeDragStart(event) {
+    event.dataTransfer.setData("Text", event.target.id);
+}
+
+function savePositions(el) {
+    graph_editor.savePositions();
+}
+
+function buildPalette(args) {
+    $("#paletteContainer").empty();
+    var type_property = graph_editor.getTypeProperty();
+    if (args.length > 0) {
+        args.forEach(function (category) {
+
+            var category_id = "category_" + category.category_name.replace(/[\s.*+?^${}()\/|[\]\\]/g, "_");//.replace(/\s/g, '');
+            var content_id = "palette-content-" + category.category_name.replace(/[\s.*+?^${}()\/|[\]\\]/g, "_");//.replace(/\s/g, '');
+
+            $("#paletteContainer").append('<div id="' + category_id + '" class="palette-category" ><div class="palette-header" onClick="handlePaletteCat(this);" category_id="' + category_id + '"> ' +
+                '<i class="fa fa-chevron-down "></i>' +
+                '<span>  ' + category.category_name + '</span>' +
+                '</div>' +
+                '<div id="' + content_id + '" class="palette-content">' +
+
+                '</div>' +
+                '</div>');
+            category.types.forEach(function (type) {
+                console.log(graph_editor.get_name_from_d3_symbol(d3.symbolCircle))
+                var type_id = type.id.replace(/[\s.*+?^${}()|[\]\\]/g, "_");
+                var palette_node_icon;
+                if (type_property[type.id] && type_property[type.id].image && type_property[type.id].image != '') {
+                    palette_node_icon = '<div class="palette-node-icon" style="background-image: url(' + (type_property[type.id].image || "") + ')"></div>';
+                }
+                else if (type_property[type.id] && type_property[type.id].shape) {
+                    palette_node_icon = buildHtmlShape({
+                        shape: type_property[type.id].shape,
+                        color: type_property[type.id].color
+                    });
+
+                }
+                else {//#1F77B4
+                    palette_node_icon = '<div class="palette-node-icon"> <div class="palette-node-square" style="background:#1F77B4;"></div></div>';
+                }
+
+                var html_to_append = '<div class="palette-node ui-draggable" draggable="true" type-name="' + type.id + '" id="' + type_id + '" ondragstart="nodeDragStart(event)">' +
+                    '<div class="palette-node-label">' + type.name + '</div>' +
+                    '<div class="palette-node-icon-container">' +
+                    palette_node_icon +
+                    '</div>' +
+                    '</div>'
+                $("#" + content_id).append(html_to_append);
+            });
+
+        });
+    }
+    togglePaletteSpinner(true);
+
+
+}
+
+function handlePaletteCat(item) {
+    console.log("handlePaletteContainer")
+    var category_id = $(item).attr("category_id")
+    $('#' + category_id).toggleClass("palette-close");
+
+}
+
+function togglePaletteSpinner(addOrRemove) {
+    $('#palette').toggleClass("palette-status-hidden", addOrRemove);
+}
+
+function showAlert(msg) {
+    // modal_alert_text
+    var alert_msg = ""
+    if (typeof msg == "string")
+        alert_msg = msg
+    else
+        alert_msg = JSON.stringify(msg)
+    $('#modal_alert_text').text(alert_msg);
+    $('#modal_alert').modal('show');
+}
+
+function getUrlParameter(par_name) {
+    var results = new RegExp('[\?&]' + par_name + '=([^&#]*)').exec(window.location.href);
+    if (results == null) {
+        return null;
+    } else {
+        return results[1] || 0;
+    }
+}
+
+function buildHtmlShape(args) {
+    var mySymbol = args.shape;
+    switch (mySymbol) {
+        case d3.symbolCircle:
+            return '<div class="palette-node-icon"> <div class="palette-node-circle" style="background:' + args.color + ';"></div></div>';
+            break;
+        case d3.symbolSquare:
+            return '<div class="palette-node-icon"> <div class="palette-node-square" style="background:' + args.color + ';"></div></div>';
+            break;
+        case d3.symbolDiamond:
+            return '<div class="palette-node-icon" style="background-color:' + args.color + '"></div>';
+            ;
+            break;
+        case d3.symbolTriangle:
+            return '<div class="palette-node-icon"> <div class="palette-node-triangle" style="border-color: transparent transparent ' + args.color + ' transparent;"></div></div>';
+            break;
+        case d3.symbolStar:
+            return '<div class="palette-node-icon" style="background-color:' + args.color + '"></div>';
+            ;
+            break;
+        case d3.symbolCross:
+            return '<div class="palette-node-icon" style="background-color:' + args.color + '"></div>';
+            ;
+            break;
+        default:
+            // if the string is not recognized
+            return "unknown";
+        //return d3.symbolCircleUnknown;
+    }
+
+
+}
+
+if (!String.format) {
+    String.format = function (format) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return format.replace(/{(\d+)}/g, function (match, number) {
+            return typeof args[number] != 'undefined' ?
+                args[number] :
+                match;
+        });
+    };
+}
\ No newline at end of file
diff --git a/static/topology3D/css/d3-context-menu.css b/static/topology3D/css/d3-context-menu.css
new file mode 100644 (file)
index 0000000..334513c
--- /dev/null
@@ -0,0 +1,78 @@
+.d3-context-menu {
+       position: absolute;
+       display: none;
+       background-color: #f2f2f2;
+       border-radius: 4px;
+
+       font-family: Arial, sans-serif;
+       font-size: 14px;
+       min-width: 150px;
+       border: 1px solid #d4d4d4;
+
+       z-index:1200;
+}
+
+.d3-context-menu ul {
+       list-style-type: none;
+       margin: 4px 0px;
+       padding: 0px;
+       cursor: default;
+}
+
+.d3-context-menu ul li {
+       padding: 4px 16px;
+
+       -webkit-touch-callout: none; /* iOS Safari */
+       -webkit-user-select: none;   /* Chrome/Safari/Opera */
+       -khtml-user-select: none;    /* Konqueror */
+       -moz-user-select: none;      /* Firefox */
+       -ms-user-select: none;       /* Internet Explorer/Edge */
+       user-select: none;
+}
+
+.d3-context-menu ul li:hover {
+       background-color: #4677f8;
+       color: #fefefe;
+}
+
+/*
+       Header
+*/
+
+.d3-context-menu ul li.is-header,
+.d3-context-menu ul li.is-header:hover {
+       background-color: #f2f2f2;
+       color: #444;
+       font-weight: bold;
+       font-style: italic;
+}
+
+/*
+       Disabled
+*/
+
+.d3-context-menu ul li.is-disabled,
+.d3-context-menu ul li.is-disabled:hover {
+       background-color: #f2f2f2;
+       color: #888;
+       cursor: not-allowed;
+}
+
+/*
+       Divider
+*/
+
+.d3-context-menu ul li.is-divider {
+       padding: 0px 0px;
+}
+
+.d3-context-menu ul li.is-divider:hover {
+       background-color: #f2f2f2;
+}
+
+.d3-context-menu ul hr {
+       border: 0;
+    height: 0;
+    border-top: 1px solid rgba(0, 0, 0, 0.1);
+    border-bottom: 1px solid rgba(255, 255, 255, 0.3);
+}
\ No newline at end of file
diff --git a/static/topology3D/css/graph_editor_d3js.css b/static/topology3D/css/graph_editor_d3js.css
new file mode 100755 (executable)
index 0000000..0433236
--- /dev/null
@@ -0,0 +1,415 @@
+
+.node_path {
+    opacity: 1;
+    stroke: #2F3550;
+    stroke-width: 1;
+}
+
+.node_selected {
+    opacity: 1 !important;
+    stroke: #2F3550 !important;
+    stroke-width: 3 !important;
+}
+
+.hidden_circle {
+    opacity: 0;
+    stroke: #FF0000 !important;
+    stroke-width: 2 !important;
+}
+
+.hidden_circle:hover {
+    opacity: 1;
+    stroke: #FF0000 !important;
+    stroke-width: 2;
+}
+
+.node_path:hover {
+    opacity: 1;
+    stroke: #2F3550;
+    stroke-width: 3;
+}
+
+.node_path:hover text {
+    opacity: 0.4;
+
+}
+
+.link {
+    fill: none;
+}
+
+.link path {
+    stroke-width: 2;
+}
+
+.matted {
+    opacity: 0.3;
+}
+
+.invisible {
+    visibility: hidden;
+}
+
+.drag_box {
+    padding: 0 40px 0 0;
+    margin-bottom: 0px !important;
+    padding-top: 0px;
+    top: 14px;
+    /* right: 25px !important; */
+    /* width: 15%; */
+    position: absolute;
+    z-index: 1;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    right: 0;
+    cursor: move;
+}
+
+.selector_box {
+    padding: 0 40px 0 0;
+    margin-bottom: 0px !important;
+    padding-top: 0px;
+    top: 70px;
+    /* right: 25px !important; */
+    /* width: 15%; */
+    position: absolute;
+    z-index: 1;
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    right: 0;
+    cursor: move;
+}
+
+.drag_button {
+    cursor: move !important;
+    /*border-style: dashed;*/
+    border-width: thin;
+    border-color: black;
+    color: white;
+    border-radius: 50% !important;
+    height: 50px !important;
+    width: 50px !important;
+    margin-right: 5px;
+}
+
+.drag_button p {
+    font-family: "Lucida Console", Monaco, monospace;;
+    font-size: 0.6em;
+    font-weight: bold;
+    text-decoration: none;
+    color: black;
+    text-align: center;
+    padding-top: 50%;
+    padding-bottom: 50%;
+    width: 100%;
+}
+
+.left-tool-bar {
+    top: 14px;
+    cursor: default;
+    line-height: 22px;
+    position: absolute;
+
+    padding: 0px 6px;
+
+    /* top: 7px; */
+    /* right: 6px; */
+    z-index: 1;
+    text-align: center;
+    font-size: 12px;
+    /*color: #777;
+    /* border-radius: 20px; */
+
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+.left-tool-bar-monitoring {
+
+    z-index: 1;
+    text-align: center;
+    font-size: 12px;
+
+    top: 64px;
+    cursor: default;
+    padding: 0 6px;
+    line-height: 22px;
+    position: absolute;
+
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+.vnffg-box {
+    top: 80px;
+    left: 100px;
+    cursor: default;
+    position: absolute;
+    width: 130px;
+
+    padding-top: 0px;
+    padding-bottom: 0px;
+
+    /* top: 7px; */
+    /* right: 6px; */
+    z-index: 1;
+    text-align: center;
+    font-size: 12px;
+    /*color: #777;
+    /* border-radius: 20px; */
+
+    -webkit-touch-callout: none;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+}
+
+textarea {
+    font-family: "Lucida Console", Monaco, monospace;
+    height: 350px !important;
+    resize: none;
+}
+
+.form-group {
+    margin-bottom: 0px !important;
+    margin-top: 0px !important;
+}
+
+.modal-body {
+    overflow-y: auto;
+}
+
+[draggable=true] {
+    -khtml-user-drag: element;
+    -webkit-user-drag: element;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+}
+
+.help-key {
+    border: 1px solid #ddd;
+    padding: 4px;
+    border-radius: 3px;
+    background: #f6f6f6;
+    font-family: Courier, monospace;
+    box-shadow: #999 1px 1px 1px;
+}
+
+/*
+Palette section
+*/
+#palette {
+    position: absolute;
+    top: 20px;
+    bottom: 20px;
+    right: 22px;
+    background: #f3f3f3;
+    width: 170px;
+    text-align: center;
+    -webkit-user-select: none;
+    -khtml-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    border: 1px solid #bbb;
+    box-sizing: border-box;
+    padding: 0 0 0 0;
+}
+
+.palette-status-indicator {
+    top: 50%;
+    position: relative;
+    justify-content: center;
+}
+
+.palette-status-hidden > .palette-status-indicator {
+    top: 50%;
+    position: relative;
+    justify-content: center;
+    display: none;
+}
+
+.palette-container {
+    #display: none;
+    position: absolute;
+    top: 0px;
+    right: 0;
+    bottom: 25px;
+    left: 0;
+    padding: 0;
+    overflow-y: auto;
+    box-sizing: border-box;
+}
+
+.palette-category {
+
+    border-bottom: 1px solid #ccc;
+}
+
+.palette-hide {
+    display: none;
+}
+
+.palette-header {
+    position: relative;
+    background: #f3f3f3;
+    cursor: pointer;
+    text-align: left;
+    padding: 1px;
+    font-weight: bold;
+    font-size: 16px;
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+}
+
+.palette-content {
+
+    background: #fff;
+    padding: 3px;
+}
+
+.palette-close > .palette-content {
+    background: #fff;
+    padding: 3px;
+    display: none;
+}
+
+.palette-close > .palette-header > i.fa.fa-chevron-down {
+
+    filter: none;
+    -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
+    -webkit-transform: rotate(-90deg);
+    -ms-transform: rotate(-90deg);
+    transform: rotate(-90deg);
+}
+
+.palette-node {
+    cursor: move;
+    margin: 3px auto;
+    height: 35px;
+    border-radius: 5px;
+    border: 1px solid #999;
+    background: #ddd no-repeat 5% 50%;
+    width: 80%;
+    background-size: contain;
+    position: relative;
+}
+
+.palette-node-label {
+    font-size: 13px;
+    font-weight: bold;
+    padding-left: 30px;
+    margin-left: auto;
+    margin-right: auto;
+    position: relative;
+    top: 50%;
+    transform: translateY(-50%);
+}
+
+.palette-node-icon-container {
+    position: absolute;
+    text-align: center;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    width: 30px;
+    border-right: 1px solid rgba(0, 0, 0, 0.1);
+    background-color: rgba(0, 0, 0, 0.05);
+}
+
+.palette-node-icon {
+    /* display: inline-block;*/
+    width: 30px;
+    height: 100%;
+    background-position: 50% 50%;
+    background-size: contain;
+    background-repeat: no-repeat;
+}
+
+.palette-node-circle {
+    display: inline-block;
+    margin-top: 2px;
+    width: 28px;
+    height: 28px;
+    -webkit-border-radius: 14px;
+    -moz-border-radius: 14px;
+    border-radius: 14px;
+    background: red;
+}
+
+.palette-node-square {
+    display: inline-block;
+    margin-top: 4px;
+    padding-top: 1px;
+    width: 26px;
+    height: 26px;
+    background: red;
+}
+
+.palette-node-triangle {
+    padding-top: 1px;
+    width: 0px;
+    height: 0px;
+    border-left: 15px solid transparent;
+    border-right: 15px solid transparent;
+    border-bottom: 30px solid #2f2f2f;
+}
+
+/*
+END Palette section
+*/
+
+/*
+Popup section
+*/
+.popup.bg {
+    fill: black;
+    opacity: 0.21;
+    border-radius: 3px;
+}
+
+.popup.summary.bg {
+    fill: white;
+    opacity: 1;
+}
+
+.popup.summary.counter {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+.popup.summary.countervalue {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+.popup.title {
+    fill: black;
+    font-size: 16px;
+    font-family: 'Source Sans Pro', 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+
+/*
+Popup section
+*/
\ No newline at end of file
diff --git a/static/topology3D/js/d3-context-menu.js b/static/topology3D/js/d3-context-menu.js
new file mode 100644 (file)
index 0000000..6f77042
--- /dev/null
@@ -0,0 +1,131 @@
+(function (root, factory) {
+    if (typeof module === 'object' && module.exports) {
+        module.exports = function (d3) {
+            d3.contextMenu = factory(d3);
+            return d3.contextMenu;
+        };
+    } else if (typeof define === 'function' && define.amd) {
+        try {
+            var d3 = require('d3');
+        } catch (e) {
+            d3 = root.d3;
+        }
+
+        d3.contextMenu = factory(d3);
+        define([], function () {
+            return d3.contextMenu;
+        });
+    } else if (root.d3) {
+        root.d3.contextMenu = factory(root.d3);
+    }
+}(this,
+    function (d3) {
+        return function (menu, opts) {
+            var openCallback,
+                closeCallback;
+
+            if (typeof opts === 'function') {
+                openCallback = opts;
+            } else {
+                opts = opts || {};
+                openCallback = opts.onOpen;
+                closeCallback = opts.onClose;
+            }
+
+            // create the div element that will hold the context menu
+            d3.selectAll('.d3-context-menu').data([1])
+                .enter()
+                .append('div')
+                .attr('class', 'd3-context-menu');
+
+            // close menu
+            d3.select('body').on('click.d3-context-menu', function () {
+                d3.select('.d3-context-menu').style('display', 'none');
+                if (closeCallback) {
+                    closeCallback();
+                }
+            });
+
+            // this gets executed when a contextmenu event occurs
+            return function (data, index) {
+                var elm = this;
+                d3.selectAll('.d3-context-menu').html('');
+                var list = d3.selectAll('.d3-context-menu')
+                    .on('contextmenu', function (d) {
+                        d3.select('.d3-context-menu').style('display', 'none');
+                        d3.event.preventDefault();
+                        d3.event.stopPropagation();
+                    })
+                    .append('ul');
+                list.selectAll('li').data(typeof menu === 'function' ? menu(data) : menu).enter()
+                    .filter(function (d) {
+                        if(opts.type_object == 'node'){
+                            if (opts.edit_mode  ||  opts.edit_mode == d.edit_mode ) {
+                                if ((d.nodes == undefined || d.nodes.length == 0) ||
+                                    (d.nodes != undefined && d.nodes.length > 0 && d.nodes.indexOf(data.info.type) > -1))
+                                    return true
+                            }
+                        }
+                        if(opts.type_object == 'link'){
+                            if (opts.edit_mode == d.edit_mode) {
+
+                                    return true
+                            }
+                        }
+                        return false;
+                    })
+                    .append('li')
+                    .attr('class', function (d) {
+                        var ret = '';
+                        if (d.divider) {
+                            ret += ' is-divider';
+                        }
+                        if (d.disabled) {
+                            ret += ' is-disabled';
+                        }
+                        if (!d.action) {
+                            ret += ' is-header';
+                        }
+                        //if()
+                        return ret;
+                    })
+                    .html(function (d) {
+                        if (d.divider) {
+                            return '<hr>';
+                        }
+                        if (!d.title) {
+                            console.error('No title attribute set. Check the spelling of your options.');
+                        }
+                        return (typeof d.title === 'string') ? d.title : d.title(data);
+                    })
+                    .on('click', function (d, i) {
+                        if (d.disabled) return; // do nothing if disabled
+                        if (!d.action) return; // headers have no "action"
+                        d.action(elm, data, index);
+                        d3.select('.d3-context-menu').style('display', 'none');
+
+                        if (closeCallback) {
+                            closeCallback();
+                        }
+                    });
+
+                // the openCallback allows an action to fire before the menu is displayed
+                // an example usage would be closing a tooltip
+                if (openCallback) {
+                    if (openCallback(data, index) === false) {
+                        return;
+                    }
+                }
+
+                // display context menu
+                d3.select('.d3-context-menu')
+                    .style('left', (d3.event.pageX - 2) + 'px')
+                    .style('top', (d3.event.pageY - 2) + 'px')
+                    .style('display', 'block');
+
+                d3.event.preventDefault();
+                d3.event.stopPropagation();
+            };
+        };
+    }
+));
\ No newline at end of file
diff --git a/static/topology3D/js/event.js b/static/topology3D/js/event.js
new file mode 100755 (executable)
index 0000000..cfe815c
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+
+if (typeof dreamer === 'undefined') {
+    var dreamer = {};
+}
+
+dreamer.Event = (function (global) {
+    'use strict';
+
+    function Event () {
+        this._listeners = {};     
+    }
+
+    Event.prototype.addL = function (type, listener) {
+        if (typeof this._listeners[type] == "undefined"){
+            this._listeners[type] = [];
+        }
+        this._listeners[type].push(listener);
+    };
+
+    Event.prototype.fire = function (event, args) {
+        if (typeof event == "string"){
+            event = { type: event };
+        }
+        if (!event.target){
+            event.target = this;
+        }
+
+        if (!event.type){  //falsy
+            throw new Error("Event object missing 'type' property.");
+        }
+
+        if (this._listeners[event.type] instanceof Array){
+            var listeners = this._listeners[event.type];
+            for (var i=0, len=listeners.length; i < len; i++){
+                listeners[i].call(this, event, args);
+            }
+        }
+    };
+
+    Event.prototype.addListener = function (type, listener) {
+        if (this._listeners[type] instanceof Array){
+            var listeners = this._listeners[type];
+            for (var i=0, len=listeners.length; i < len; i++){
+                if (listeners[i] === listener){
+                    listeners.splice(i, 1);
+                    break;
+                }
+            }
+        }
+
+    };
+
+    return Event;
+}());
+
+if (typeof module === 'object') {
+    module.exports = dreamer.Event;
+}
diff --git a/static/topology3D/js/graph_editor.js b/static/topology3D/js/graph_editor.js
new file mode 100755 (executable)
index 0000000..36e7c8e
--- /dev/null
@@ -0,0 +1,1084 @@
+/*
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+if (typeof dreamer === 'undefined') {
+    var dreamer = {};
+}
+var level = {}
+
+dreamer.GraphEditor = (function (global) {
+    'use strict';
+
+    var DEBUG = true;
+    var SHIFT_BUTTON = 16;
+    var CANC_BUTTON = 46;
+    var default_link_color = "#888";
+    var nominal_text_size = 15;
+    var nominal_stroke = 1.5;
+    var EventHandler = dreamer.Event;
+    //    var IMAGE_PATH = "/static/assets/img/";
+
+
+
+    /**
+     * Constructor
+     */
+    function GraphEditor(args) {
+        log("Constructor");
+        this.eventHandler = new EventHandler();
+        this.lastKeyDown = -1;
+        this._selected_node = undefined;
+        this._selected_link = undefined;
+        this._edit_mode = true;
+        this.filter_parameters = {
+            node: {
+                type: [],
+                group: [],
+            },
+            link: {
+                group: [],
+                view: [],
+            }
+        };
+        this.current_view_id = '';
+        // graph data initailization
+        this.d3_graph = {
+            nodes: [],
+            links: [],
+            graph_parameters: {}
+
+        };
+
+
+    }
+
+
+
+    GraphEditor.prototype.init = function (args) {
+        args = args || {}
+        var self = this;
+        this.width = 550//args.width || 500;
+        this.height = 550// args.height || 500;
+        this.forceSimulationActive = false;
+
+        //FixMe
+        this.width = this.width - this.width * 0.007;
+        this.height = this.height - this.height * 0.07;
+
+        //console.log("this.width", this.width, "this.height", this.height);
+        var min_zoom = 0.1;
+        var max_zoom = 7;
+        this._setupBehaviorsOnEvents();
+        this._setupFiltersBehaviors(args);
+
+        this.type_property = {
+            "unrecognized": {
+                "shape": d3.symbolCircle,
+                "color": "white",
+                "node_label_color": "black",
+                "size": 15
+            },
+        };
+
+        this.type_property_link = {
+            "unrecognized": {
+                "color": "#888",
+                //"color": "red",
+            },
+        };
+
+        this.force = d3.forceSimulation()
+            .force("collide", d3.forceCollide().radius(40))
+            .force("link", d3.forceLink().distance(80).iterations(1).id(function (d) {
+                return d.id;
+            }))
+            .force("center", d3.forceCenter(this.width / 2, this.height / 2));
+
+        var zoom = d3.zoom().scaleExtent([min_zoom, max_zoom])
+
+        var size = d3.scalePow().exponent(2)
+            .domain([1, 100])
+            .range([8, 24]);
+
+        this.svg = d3.select("#graph_ed_container").append("svg")
+            .attr("id", "graph_svg")
+            .attr("perserveAspectRatio", "xMinYMid")
+            .attr("width", this.width)
+            .attr("height", this.height);
+
+        //End Arrow style
+        this.defs = this.svg.append("svg:defs");
+
+        this.defs.selectAll("marker")
+            .data(["unrecognized"]) // Different link/path types can be defined here
+            .enter().append("svg:marker") // This section adds in the arrows
+            .attr("id", String)
+            .attr("viewBox", "-5 -5 10 10")
+            .attr("refX", 13) //must be smarter way to calculate shift
+            .attr("refY", 0)
+            .attr("markerUnits", "userSpaceOnUse")
+            .attr("markerWidth", 12)
+            .attr("markerHeight", 12)
+            .attr("orient", "auto")
+            .append("path")
+            .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
+            .attr('fill', this.type_property_link['unrecognized']['color']);
+
+        d3.select(window)
+            .on('keydown', function () {
+                log('keydown ' + d3.event.keyCode);
+                //d3.event.preventDefault();
+                if (self.lastKeyDown !== -1) return;
+                self.lastKeyDown = d3.event.keyCode;
+                if (self.lastKeyDown === CANC_BUTTON && self._selected_node != undefined) {
+                    self.removeNode(self._selected_node, null, showAlert);
+                } else if (self.lastKeyDown === CANC_BUTTON && self._selected_link != undefined) {
+                    self.removeLink(self._selected_link, null, showAlert);
+                }
+
+            })
+            .on('keyup', function () {
+                log('keyup' + self.lastKeyDown);
+                self.lastKeyDown = -1;
+            });
+        var popup = this.svg.append("g")
+            .attr("id", "popup")
+            .attr("class", "popup")
+            .attr("opacity", "0")
+            .attr("transform", "translate(1 1)")
+            .call(d3.drag()
+        .on("start", dragstarted)
+        .on("drag", dragged)
+        .on("end", dragended));
+
+        function dragstarted(d) {
+          //d3.select(this).raise().classed("active", true);
+        }
+
+        function dragged(d) {
+            //console.log(JSON.stringify(d))
+          d3.select(this).attr("transform", function () {
+                return "translate("+d3.event.x+","+d3.event.y+")";
+
+            })
+        }
+
+        function dragended(d) {
+          //d3.select(this).classed("active", false);
+        }
+
+        var chart = $("#graph_svg");
+        this.aspect = chart.width() / chart.height();
+        this.container = chart.parent();
+        $(window).on("resize", function() {
+
+            var palette_width = $("#palette").width()
+            var working_width = self.container.width() - palette_width;
+            self.width = (working_width < 0) ? 0 : working_width;
+            self.height = self.container.height();
+            chart.attr("width", self.width);
+            chart.attr("height", self.height);
+        }).trigger("resize");
+
+    }
+
+
+    GraphEditor.prototype.get_d3_symbol =
+        function (myString) {
+            log(myString)
+            switch (myString) {
+            case "circle":
+                return d3.symbolCircle;
+                break;
+            case "square":
+                return d3.symbolSquare;
+                break;
+            case "diamond":
+                return d3.symbolDiamond;
+                break;
+            case "triangle":
+                return d3.symbolTriangle;
+                break;
+            case "star":
+                return d3.symbolStar;
+                break;
+            case "cross":
+                return d3.symbolCross;
+                break;
+            default:
+                // if the string is not recognized
+                return d3.symbolCross;
+                //return d3.symbolCircleUnknown;
+            }
+
+        }
+
+     GraphEditor.prototype.get_name_from_d3_symbol =
+        function (mySymbol) {
+            //log(myString)
+            switch (mySymbol) {
+            case d3.symbolCircle:
+                return "circle";
+                break;
+            case d3.symbolSquare:
+                return "square";
+                break;
+            case d3.symbolDiamond:
+                return "diamond";
+                break;
+            case d3.symbolTriangle:
+                return "triangle";
+                break;
+            case d3.symbolStar:
+                return "star";
+                break;
+            case d3.symbolCross:
+                return "cross";
+                break;
+            default:
+                // if the string is not recognized
+                return "unknown";
+                //return d3.symbolCircleUnknown;
+            }
+
+        }
+
+    /**
+     * Start or Stop force layout
+     * @param {boolean} Required. Value true: start, false: stop
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.handleForce = function (start) {
+        if (start)
+            this.force.stop();
+        this.forceSimulationActive = start;
+        this.node.each(function (d) {
+            d.fx = (start) ? null : d.x;
+            d.fy = (start) ? null : d.y;
+        });
+
+        if (start)
+            this.force.restart();
+
+        this.eventHandler.fire("force_status_changed_on", start);
+    };
+
+    /**
+     * Handle the parameters of basic filters: node type, view, group
+     * @param {Object} Required.
+     *
+     */
+    GraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
+        console.log("handleFiltersParams", filtersParams)
+        this.filter_parameters = (filtersParams != undefined) ? filtersParams : this.filter_parameters;
+        this.current_view_id = (this.filter_parameters != undefined && this.filter_parameters.link.view[0] != undefined) ? this.filter_parameters.link.view[0] : this.current_view_id
+        this.cleanAll();
+        this.refresh();
+        this.startForce();
+        this.force.restart();
+        this._deselectAllNodes();
+        this.handleForce(this.forceSimulationActive);
+        if (!notFireEvent)
+            this.eventHandler.fire("filters_changed", filtersParams);
+
+    };
+
+    /**
+     * Add a new node to the graph.
+     * @param {Object} Required. An object that specifies tha data of the new node.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.addNode = function (args) {
+        if (args.id && args.info && args.info.type) {
+            args.fixed = true;
+            this.force.stop();
+            this.cleanAll();
+            this.d3_graph.nodes.push(args);
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+            this.handleForce(this.forceSimulationActive);
+            return true;
+        }
+
+        return false;
+
+    };
+
+    /**
+     * Update the data properties of the node
+     * @param {Object} Required. An object that specifies tha data of the node.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.updateDataNode = function (args) {
+
+    };
+
+    /**
+     * Remove a node from graph and related links.
+     * @param {String} Required. Id of node to remove.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.removeNode = function (node) {
+        if (node != undefined) {
+            var node_id = node.id;
+            this.d3_graph['nodes'].forEach(function (n, index, object) {
+                if (n.id == node_id) {
+                    object.splice(index, 1);
+
+                }
+
+            });
+            //TODO trovare una metodo piu efficace
+            var self = this;
+            var links_to_remove = [];
+            this.d3_graph['links'].forEach(function (l, index, object) {
+                if (node_id === l.source.id || node_id === l.target.id) {
+                    links_to_remove.push(index);
+                }
+
+            });
+            var links_removed = 0;
+            links_to_remove.forEach(function (l_index) {
+                self.d3_graph['links'].splice(l_index - links_removed, 1);
+                links_removed++;
+            });
+            this.cleanAll();
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+
+            return true;
+        }
+        return false;
+    };
+
+
+    /**
+     * Add a new link to graph.
+     * @param {Object} Required. An object that specifies tha data of the new Link.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.addLink = function (link) {
+        console.log(JSON.stringify(link))
+        if (link.source && link.target) {
+            this.force.stop();
+            this.cleanAll();
+            this.d3_graph.links.push(link);
+            this.refresh();
+            this.startForce();
+            this.force.restart();
+            return true;
+        }
+
+        return false;
+    };
+
+    /**
+     * Remove a link from graph.
+     * @param {String} Required. The identifier of link to remove.
+     * @returns {boolean}
+     */
+    GraphEditor.prototype.removeLink = function (link_id) {
+        var self = this;
+        if (link_id !== 'undefined') {
+            this.d3_graph['links'].forEach(function (l, index, object) {
+                if (link_id === l.index) {
+                    object.splice(index, 1);
+
+                    self.cleanAll();
+                    self.refresh();
+                    self.startForce();
+                    self.force.restart();
+                    return true;
+                }
+
+            });
+        }
+
+        return false;
+    };
+
+
+    /**
+     * Force a refresh of GraphView
+     * @returns {}
+     */
+    GraphEditor.prototype.refresh = function () {
+
+        //log(data)
+        var self = this;
+
+        this.link = this.svg
+            .selectAll()
+            .data(self.d3_graph.links
+                .filter(this.link_filter_cb)
+            )
+            .enter().append("g")
+            .attr("class", "link cleanable")
+            .append("path")
+            .attr("class", "link")
+            .attr("class", "cleanable")
+            .style("stroke-width", nominal_stroke)
+            .style("stroke", function (d) {
+                return self._link_property_by_type((d.type_link) ? d.type_link : "unrecognized", "color");
+            })
+            .attr("marker-end", function (d) {
+                if (!d.directed_edge)
+                    return '';
+
+                var marker_url = (d.type_link) ? d.type_link : "unrecognized"
+                return (d.directed_edge ? "url(#" + marker_url + ")" : '');
+            });
+
+        this.nodeContainer = this.svg
+            .selectAll()
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+            .enter()
+            .append("g")
+            // .attr("class", "nodosdads")
+            .attr("class", "node cleanable");
+
+        this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+
+            .filter(function (d) {
+                return (d.info.type == undefined) || (self._node_property_by_type(d.info.type, 'image', d) == undefined)
+            })
+
+            .append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d), 2) / 4;
+                })
+                .type(function (d) {
+                    // console.log(d.info.type, 'shape', self.current_view_id)
+                    return (self._node_property_by_type(d.info.type, 'shape', d));
+                })
+            )
+            .style("fill", function (d) {
+                return self._node_property_by_type(d.info.type, 'color', d);
+            })
+            .attr("transform", function () {
+                return "rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+
+            .attr("class", "node_path")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+        var figure_node = this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+
+            .filter(function (d) {
+                return self._node_property_by_type(d.info.type, 'image', d) != undefined
+            });
+
+        figure_node.append("svg:image")
+            .attr("xlink:href", function (d) {
+                return self._node_property_by_type(d.info.type, 'image', d)
+            })
+            .attr("x", function (d) {
+                return -self._node_property_by_type(d.info.type, 'size', d) / 2
+            })
+            .attr("y", function (d) {
+                return -self._node_property_by_type(d.info.type, 'size', d) / 2
+            })
+            .attr("width", function (d) {
+                return self._node_property_by_type(d.info.type, 'size', d)
+            })
+            .attr("height", function (d) {
+                return self._node_property_by_type(d.info.type, 'size', d)
+            })
+            .style("stroke", "black")
+            .style("stroke-width", "1px")
+
+            .attr("class", "node_path")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+        figure_node.append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return Math.PI * Math.pow(self._node_property_by_type(d.info.type, 'size', d) + 7, 2) / 4;
+                })
+                .type(function (d) {
+                    return (self.get_d3_symbol('circle'));
+                })
+            )
+            .style("fill", 'transparent')
+            .attr("transform", function () {
+                return "rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+
+            .attr("class", "hidden_circle")
+            .attr("id", function (d) {
+                return "path_" + d.id;
+            })
+
+            .call(d3.drag()
+                .on("start", dragstarted)
+                .on("drag", dragged)
+                .on("end", dragended));
+
+
+
+        this.node = this.svg.selectAll('.node')
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb)).selectAll("image, path, circle");
+
+
+
+        this.node.on("contextmenu", self.behavioursOnEvents.nodes["contextmenu"])
+            .on("mouseover", self.behavioursOnEvents.nodes["mouseover"])
+            .on("mouseout", self.behavioursOnEvents.nodes["mouseout"])
+            .on('click', self.behavioursOnEvents.nodes["click"])
+            .on('dblclick', self.behavioursOnEvents.nodes["dblclick"]);
+
+        this.link
+            .on("contextmenu", self.behavioursOnEvents.links["contextmenu"])
+            .on("mouseover", self.behavioursOnEvents.links["mouseover"])
+            .on('click', self.behavioursOnEvents.links["click"])
+            .on("mouseout", self.behavioursOnEvents.links["mouseout"]);
+
+
+
+        this.text = this.svg.selectAll(".node")
+            .data(self.d3_graph.nodes
+                .filter(this.node_filter_cb))
+            .append("svg:text")
+            .attr("class", "nodetext")
+            .attr("class", "cleanable")
+            .attr("dy", function(d) { 
+                if (self._node_property_by_type(d.info.type, 'image', d) == undefined) {
+                    //shape
+                    return "-5"
+                }
+                else {
+                    //image
+                    return (-self._node_property_by_type(d.info.type, 'size', d)/2).toString()
+                }
+            })
+            .attr("pointer-events", "none")
+            .style("font-size", nominal_text_size + "px")
+            .style("font-family", "Lucida Console")
+            .style("fill", function (d) {
+                return self._node_property_by_type(d.info.type, 'node_label_color', d);
+            })
+            .style("text-anchor", "middle")
+            .text(function (d) {
+                return d.id;
+            });
+
+
+
+        function dragstarted(d) {
+            d.draggednode = true;
+            if (!d3.event.active) self.force.alphaTarget(0.3).restart();
+            d.fx = d.x;
+            d.fy = d.y;
+
+        }
+
+        function dragged(d) {
+            d.fx = d3.event.x;
+            d.fy = d3.event.y;
+        }
+
+        function dragended(d) {
+            d.draggednode = false;
+            if (!d3.event.active) self.force.alphaTarget(0);
+            if (self.forceSimulationActive) {
+                d.fx = null;
+                d.fy = null;
+            } else {
+                d.fx = d.x;
+                d.fy = d.y;
+                self.force.stop();
+                self.forceSimulationActive = false;
+            }
+        }
+
+
+    };
+
+    /**
+     *  Start force layout on Graph.
+     *
+     */
+    GraphEditor.prototype.startForce = function () {
+        //this.force.stop();
+        var self = this
+        this.force
+            .nodes(this.d3_graph.nodes)
+            .on("tick", ticked);
+
+
+        this.force
+            .force("link")
+            .links(this.d3_graph.links);
+
+        function ticked() {
+            self.node.attr("cx", function (d) {
+                    return d.x = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.width - self._node_property_by_type(d.info.type, 'size', d), d.x));
+                })
+                .attr("cy", function (d) {
+                    return d.y = Math.max(self._node_property_by_type(d.info.type, 'size', d), Math.min(self.height - self._node_property_by_type(d.info.type, 'size', d), d.y));
+                });
+
+            self.link.attr("d", function (d) {
+                var dx = d.target.x - d.source.x,
+                    dy = d.target.y - d.source.y,
+                    dr = Math.sqrt(dx * dx + dy * dy);
+                return "M" + d.source.x + "," + d.source.y + "," + d.target.x + "," + d.target.y;
+            });
+
+            self.node.attr("transform", function (d) {
+                return "translate(" + d.x + "," + d.y + ")";
+            });
+            self.text.attr("transform", function (d) {
+                var label_pos_y = d.y + self._node_property_by_type(d.info.type, 'size', d) + 10;
+                return "translate(" + d.x + "," + label_pos_y + ")";
+            });
+        };
+
+
+
+    };
+
+    /**
+     * This method attaches an event handler.
+     * @param {String} Required. A String that specifies the name of the event.
+     * @param {Function} Required. Specifies the function to run when the event occurs.
+     * @returns {}
+     */
+    GraphEditor.prototype.addListener = function (event_name, cb) {
+        this.eventHandler.addL(event_name, cb);
+    }
+
+    /**
+     * This method removes an event handler that has been attached with the addListener() method.
+     * @param {String} Required. A String that specifies the name of the event to remove.
+     * @param {Function} Required. Specifies the function to remove.
+     * @returns {}
+     */
+    GraphEditor.prototype.removeListener = function (event_name, cb) {
+
+    }
+
+
+    GraphEditor.prototype.setNodeClass = function (class_name, filter_cb) {
+        log("setNodeClass");
+        var self = this;
+        this.svg.selectAll('.node').classed(class_name, false);
+        this.svg.selectAll('.node')
+            .classed(class_name, filter_cb);
+    }
+
+    GraphEditor.prototype.setLinkClass = function (class_name, filter_cb) {
+        log("setLinkClass");
+        var self = this;
+        this.svg.selectAll('.link').classed(class_name, false);
+        this.svg.selectAll('.link')
+            .classed(class_name, filter_cb);
+    }
+
+    GraphEditor.prototype.showNodeInfo = function(args){
+        this.addLinesToPopup(args['node_info'], "Info about node selected")
+        this.handlePopupVisibility(true, 'right')
+    }
+    GraphEditor.prototype.addLinesToPopup = function(data, title) {
+        var self = this;
+        var index = 1;
+        var translate_y = 0;
+        var width_popup = 400;
+        var height_popup = 0;
+
+        d3.selectAll(".popupcleanable").remove(); // clean
+
+        var popupbg = d3.select(".popup").append("rect")
+            .attr("id", "popupbg")
+            .attr("class", "popup bg popupcleanable cleanable")
+            .attr("width", "400")
+            .attr("height", "0")
+            .attr("rx", 10) // set the x corner curve radius
+            .attr("ry", 10); // set the y corner curve radius
+
+
+        d3.select(".popup").append("svg:path")
+            .attr("d", d3.symbol()
+                .size(function (d) {
+                    return 80
+                })
+                .type(function (d) {
+                    return (self.get_d3_symbol());
+                })
+            )
+            .style("fill", 'red')
+            .attr("transform", function () {
+                return "translate(380,15) rotate(-45)";
+
+            })
+            .attr("stroke-width", 2.4)
+            .attr("id", "close_popup")
+            .attr("class", "popupcleanable cleanable")
+            .on("click", function(d) {
+                self.handlePopupVisibility(false);
+            });
+
+        d3.select(".popup").append("text")
+            .attr("class", "popup title popupcleanable cleanable")
+            .attr("x", "10")
+            .attr("y", "20")
+            .text(title);
+
+        for (var i in data) {
+            //console.log(i, data, data[i])
+            //var typeofvalue = typeof data[i];
+            var record = data[i];
+            index = this._addRecordToPopup(i, record,index)
+
+        }
+
+    };
+
+    GraphEditor.prototype._addRecordToPopup = function (key, record, index, tab) {
+        //console.log("_addRecordToPopup", key, record, index)
+        var translate_y = 23 * index;
+            var summary = d3.select(".popup").append("g")
+                    .attr("class", "popup summary d popupcleanable cleanable")
+                    .attr("transform", "translate(10 " + translate_y + ")");
+        if(Object.prototype.toString.call( record ) !== '[object Array]'){ //is a record simple key:value
+            //console.log(key, record)
+            var summary_g = summary.append("g");
+                summary_g.append("rect")
+                    .attr("class", "popup summary bg popupcleanable cleanable")
+                    .attr("width", "380")
+                    .attr("height", "20");
+
+                summary_g.append("text")
+                    .attr("class", "popup summary  popupcleanable cleanable")
+                    .attr("x", (tab)? tab: 10)
+                    .attr("y", "17")
+                    .attr("width", "100")
+                    .text(function(d){
+                        return key.toUpperCase() + ":";
+                    });
+
+                summary_g.append("text")
+                    .attr("class", "popup summary  popupcleanable cleanable")
+                    .attr("x", "370")
+                    .attr("y", "17")
+                    .attr("text-anchor", "end")
+                    .text(function(d){return record});
+        }
+        else {//is a record simple complex: have a list of sub record key:value
+                //index ++;
+                this._addRecordToPopup(key, "", index)
+                for(var r in record){
+                    //console.log(i, r, record, record[r])
+                    for(var k in record[r]){
+                        //console.log(i, r, k, record[r][k])
+                        var curr_key = k;
+                        var recordValue = record[r][k]
+
+                        index ++;
+                        this._addRecordToPopup(curr_key, recordValue, index, 20)
+                    }
+                }
+
+        }
+
+        translate_y = 30 * index++;
+        d3.select('#popupbg').attr("height", translate_y);
+        return index;
+    };
+
+
+
+    /**
+     *  Remove all the graph objects from the view
+     */
+    GraphEditor.prototype.cleanAll = function () {
+        this.svg.selectAll('.cleanable').remove();
+    };
+
+    /**
+     *  Internal functions
+     */
+
+    GraphEditor.prototype._node_property_by_type = function (type, property, node) {
+        //console.log(type, property, layer, group)
+        var unrecognized = function (ui_prop, property) {
+            return ui_prop['unrecognized'][property]
+        };
+
+        //type recognized
+        if (this.type_property[type]) {
+
+            if (this.type_property[type]['property']) {
+                var filt_property = this.type_property[type]['property']
+                return this.type_property[type][node.info[filt_property]][property]
+            } else { // type without property spec
+
+                return this.type_property[type][property]
+
+            }
+
+        } else { //type unrecognized
+            return unrecognized(this.type_property, property)
+        }
+
+    };
+
+    GraphEditor.prototype._link_property_by_type = function (type, property) {
+        //log(type + "-" + property)
+        if (this.type_property_link[type] != undefined && this.type_property_link[type][property] != undefined) {
+            //if(property == "shape")
+            //    log("dentro" + this.type_property[type][property])
+            return this.type_property_link[type][property];
+        } else {
+            return this.type_property_link['unrecognized'][property];
+        }
+
+    }
+
+
+    /**
+     *
+     *
+     *
+     */
+    GraphEditor.prototype._setupFiltersBehaviors = function (args) {
+
+        var self = this;
+
+        this.node_filter_cb = args.node_filter_cb || function (d) {
+
+            var cond_view = true,
+                cond_group = true;
+            //log(d.info.type + " " + self.filter_parameters.node.type + " group: " + self.filter_parameters.node.group + "- " + d.info.group)
+            // check filter by node type
+            if (self.filter_parameters.node.type.length > 0) {
+
+                if (self.filter_parameters.node.type.indexOf(d.info.type) < 0)
+                    cond_view = false;
+            }
+
+            // check filter by group
+            if (self.filter_parameters.node.group.length > 0) {
+                self.filter_parameters.node.group.forEach(function (group) {
+                    if (d.info.group.indexOf(group) < 0)
+                        cond_group = false;
+                });
+
+
+            }
+
+
+            return cond_view && cond_group;
+        };
+
+        this.link_filter_cb = args.link_filter_cb || function (d) {
+            var cond_view = true,
+                cond_group = true;
+
+            // check filter by view
+            if (self.filter_parameters.link.view.length > 0) {
+                self.filter_parameters.link.view.forEach(function (view) {
+                    if (d.view.indexOf(view) < 0)
+                        cond_view = false;
+                });
+            }
+
+            // check filter by group
+            if (self.filter_parameters.link.group.length > 0) {
+                self.filter_parameters.link.group.forEach(function (group) {
+                    if (d.group.indexOf(group) < 0)
+                        cond_group = false;
+                });
+            }
+            return cond_view && cond_group;
+        };
+
+    };
+
+    /**
+     *
+     *
+     */
+    GraphEditor.prototype._setupBehaviorsOnEvents = function () {
+        log("_setupBehaviorsOnEvents");
+        var self = this;
+        this.behavioursOnEvents = {
+            'nodes': {
+                'click': function (d) {
+                    d3.event.preventDefault();
+                    log('click', d);
+                    if (self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) {
+                        var source_id = self._selected_node.id;
+                        var target_id = d.id;
+                        log(JSON.stringify(self.filter_parameters.link.view));
+                        var new_link = {
+                            source: source_id,
+                            target: target_id,
+                            view: self.filter_parameters.link.view[0],
+                            group: self.filter_parameters.link.group[0],
+                        };
+                        self.addLink(new_link);
+                        self._deselectAllNodes();
+                    } else {
+                        self._selectNodeExclusive(this, d);
+                    }
+
+                },
+                'mouseover': function (d) {
+
+                },
+                'mouseout': function (d) {},
+                'dblclick': function (d) {
+                    d3.event.preventDefault();
+                    log('dblclick');
+                },
+                'contextmenu': function (d, i) {
+                    d3.event.preventDefault();
+                    log("contextmenu node");
+                    self.eventHandler.fire("right_click_node", d);
+                }
+            },
+            'links': {
+                'click': function (event) {
+
+                },
+                'dblclick': function (event) {
+
+                }
+            }
+        };
+    };
+
+    /**
+     *  Deselect previously selected nodes
+     *
+     */
+    GraphEditor.prototype._deselectAllNodes = function () {
+        log("_deselectAllNodes");
+        this.node.classed("node_selected", false);
+        this._selected_node = undefined;
+    };
+
+    GraphEditor.prototype._deselectAllLinks = function () {
+        log("_deselectAllLinks");
+        this.link.classed("link_selected", false).style('stroke-width', 2);
+        this._selected_link = undefined;
+    };
+    /**
+     *  Select node in exclusive mode
+     *  @param {Object} Required. Element selected on click event
+     */
+    GraphEditor.prototype._selectNodeExclusive = function (node_instance, node_id) {
+        log("_selectNodeExclusive ");
+        var activeClass = "node_selected";
+        var alreadyIsActive = d3.select(node_instance).classed(activeClass);
+        this._deselectAllNodes();
+        this._deselectAllLinks();
+        d3.select(node_instance).classed(activeClass, !alreadyIsActive);
+        this._selected_node = (alreadyIsActive) ? undefined : node_instance.__data__;
+    };
+
+    /**
+     *  Select node in exclusive mode
+     *  @param {Object} Required. Element selected on click event
+     */
+    GraphEditor.prototype._selectLinkExclusive = function (link_instance, link_id) {
+        log("_selectLinkExclusive ");
+        var activeClass = "link_selected";
+        var alreadyIsActive = d3.select(link_instance).classed(activeClass);
+        this._deselectAllNodes();
+        this._deselectAllLinks();
+        d3.select(link_instance).classed(activeClass, !alreadyIsActive);
+        d3.select(link_instance).style('stroke-width', 4)
+        this._selected_link = link_instance.__data__;
+    };
+
+    /**
+     *  Callback to resize SVG element on window resize
+     */
+    GraphEditor.prototype.resizeSvg = function (width, height) {
+        log("resizeSvg");
+        log(event);
+        this.width = width || this.width;
+        this.height = height || this.height;
+        this.svg.attr('width', width);
+        this.svg.attr('height', height);
+
+    }
+
+    GraphEditor.prototype.handlePopupVisibility = function(visible, side) {
+        var opacity = (visible) ? 1 : 0;
+
+        var translate_op = (side == "left") ? "translate(50 50)" : "translate("+(this.width - 450).toString()+" 50)";
+
+        if (!visible) {
+            d3.selectAll(".popupcleanable").remove();
+            d3.select(".popup")
+                .attr("transform", "translate(-1 -1)");
+        } else {
+            d3.select(".popup")
+                .attr("transform", translate_op);
+        }
+        d3.select(".popup").attr("opacity", opacity);
+    };
+
+    GraphEditor.prototype.refreshGraphParameters = function (graphParameters) {
+        this.eventHandler.fire("refresh_graph_parameters", graphParameters);
+    }
+
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::GraphEditor::", text);
+    }
+
+
+
+    return GraphEditor;
+
+
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.GraphEditor;
+}
\ No newline at end of file
diff --git a/static/topology3D/js/graph_request.js b/static/topology3D/js/graph_request.js
new file mode 100644 (file)
index 0000000..055fc1c
--- /dev/null
@@ -0,0 +1,318 @@
+/*
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+
+if (typeof dreamer === 'undefined') {
+    var dreamer = {};
+}
+var level = {}
+
+dreamer.GraphRequests = (function(global) {
+    'use strict';
+
+    var DEBUG = true;
+
+    GraphRequests.prototype.constructor = GraphRequests;
+
+    /**
+     * Constructor
+     */
+    function GraphRequests(args) {
+
+
+    }
+
+    GraphRequests.prototype.addNode = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+
+        data = args_to_formdata(args, data);
+
+        //FIXME questo metodo dovrebbere essere generico
+        if(args.existing_element)
+            data.append('existing_element', args.existing_element ? args.existing_element : false)
+        //if (choice)
+        //    data.append('choice', choice);
+        $.ajax({
+            url: "addelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.removeNode = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "removeelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.getNodeOverview = function(args, success, error) {
+
+        var params = jQuery.param(args)
+        console.log("params", params)
+        $.ajax({
+            url: "overviewelement?"+params,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.addLink = function(args, choice, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data = args_to_formdata(args, data);
+
+        //data.append('destination', JSON.stringify(destination));
+        if (choice)
+            data.append('choice', choice);
+        //if(link.desc_id)
+        //    data.append('element_desc_id', link.desc_id || '');
+        $.ajax({
+            url: "addlink",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.removeLink = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "removelink",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    //
+    GraphRequests.prototype.getAvailableNodes = function(args, success, error){
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        $.ajax({
+            url: "availablenodes?layer="+args.layer,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    }
+
+    GraphRequests.prototype.savePositions = function(positions, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        data.append('positions', JSON.stringify(positions));
+        $.ajax({
+            url: "positions",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success();
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    /*  START ETSI methods  */
+    GraphRequests.prototype.addVnffg = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+       /* data.append('group_id', args.info.group[0]);
+        data.append('element_id', args.id);
+        data.append('element_type', args.info.type);*/
+        data = args_to_formdata(args, data);
+        $.ajax({
+            url: "addelement",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.addNodeToVnffg = function(args, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+       /* data.append('group_id', args.info.group[0]);
+        data.append('element_id', args.id);
+        data.append('element_type', args.info.type);
+        data.append('vnffg_id', args.vnffgId);*/
+        data = args_to_formdata(args, data);
+
+        $.ajax({
+            url: "addnodetovnffg",
+            type: 'POST',
+            data: data,
+            cache: false,
+            contentType: false,
+            processData: false,
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+    };
+
+    GraphRequests.prototype.getUnusedVnf = function(nsd_id, success, error) {
+        var data = new FormData();
+        data.append('csrfmiddlewaretoken', this.getCookie('csrftoken'));
+        $.ajax({
+            url: "unusedvnf/" + nsd_id,
+            type: 'GET',
+            success: function(result) {
+                if (success)
+                    success(result);
+            },
+            error: function(result) {
+                if (error)
+                    error(result);
+                log("some error: " + result);
+            }
+        });
+
+    };
+    /*  END ETSI methods  */
+
+    GraphRequests.prototype.getCookie = function(name) {
+        var cookieValue = null;
+        if (document.cookie && document.cookie !== '') {
+            var cookies = document.cookie.split(';');
+            for (var i = 0; i < cookies.length; i++) {
+                var cookie = jQuery.trim(cookies[i]);
+                // Does this cookie string begin with the name we want?
+                if (cookie.substring(0, name.length + 1) === (name + '=')) {
+                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
+                    break;
+                }
+            }
+        }
+        return cookieValue;
+    };
+
+    function args_to_formdata(args, form_data){
+        for ( var key in args ) {
+            form_data.append(key, args[key]);
+        }
+        return form_data;
+    };
+
+
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::GraphRequests::", text);
+    }
+
+    return GraphRequests;
+
+
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.GraphRequests;
+}
diff --git a/static/topology3D/js/model_graph_editor.js b/static/topology3D/js/model_graph_editor.js
new file mode 100644 (file)
index 0000000..3c275dd
--- /dev/null
@@ -0,0 +1,512 @@
+/*
+   Copyright 2017 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+
+   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  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.
+*/
+if (typeof dreamer === 'undefined') {
+    var dreamer = {};
+}
+var level = {}
+
+dreamer.ModelGraphEditor = (function (global) {
+    'use strict';
+
+    var DEBUG = true;
+    var SHIFT_BUTTON = 16;
+    var IMAGE_PATH = "/static/assets/img/";
+    var GUI_VERSION = "v1";
+
+
+    ModelGraphEditor.prototype = new dreamer.GraphEditor();
+    ModelGraphEditor.prototype.constructor = ModelGraphEditor;
+    ModelGraphEditor.prototype.parent = dreamer.GraphEditor.prototype;
+
+    /**
+     * Constructor
+     */
+    function ModelGraphEditor(args) {
+
+        log("Constructor");
+
+    }
+
+
+    ModelGraphEditor.prototype.init = function (args) {
+        this.parent.init.call(this, args);
+
+        if (args.gui_properties[GUI_VERSION] != undefined) {
+            args.gui_properties = args.gui_properties[GUI_VERSION];
+        }
+
+        this.desc_id = args.desc_id || undefined;
+        this.type_property = {};
+        this.type_property["unrecognized"] = args.gui_properties["default"];
+        this.type_property["unrecognized"]["default_node_label_color"] = args.gui_properties["default"]["label_color"];
+        //this.type_property["unrecognized"]["shape"] = d3.symbolCross;
+        this._edit_mode = (args.edit_mode != undefined) ? args.edit_mode : this._edit_mode;
+
+        Object.keys(args.gui_properties["nodes"]).forEach(function (key, index) {
+            this.type_property[key] = args.gui_properties["nodes"][key];
+            if ( this.type_property[key]['property'] != undefined){
+                for(var c_prop in this.type_property[key]){
+                    if(c_prop != 'property'){
+
+                        this.type_property[key][c_prop]['shape'] = this.parent.get_d3_symbol(this.type_property[key][c_prop]['shape']);
+                        if(this.type_property[key][c_prop]["image"] != undefined){
+                            this.type_property[key][c_prop]["image"] = IMAGE_PATH + this.type_property[key][c_prop]["image"]
+                        }
+                    }
+                }
+            }
+            else{
+                this.type_property[key]["shape"] = this.parent.get_d3_symbol(this.type_property[key]["shape"]);
+                if (this.type_property[key]["image"] != undefined) {
+                    this.type_property[key]["image"] = IMAGE_PATH + this.type_property[key]["image"];
+                }
+            }
+
+
+
+        }, this);
+        if(args.gui_properties["edges"]){
+            this.type_property_link = args.gui_properties["edges"];
+            var self = this;
+            var link_types = ['unrecognized'].concat(Object.keys(self.type_property_link))
+                this.defs.selectAll("marker")
+                    .data(link_types)
+                    .enter()
+                    .append("svg:marker")    // This section adds in the arrows
+                    .attr("id", function(d){
+                        return d;
+                    })
+                    .attr("viewBox", "-5 -5 10 10")
+                    .attr("refX", 13) /*must be smarter way to calculate shift*/
+                    .attr("refY", 0)
+                    .attr("markerUnits", "userSpaceOnUse")
+                    .attr("markerWidth", 12)
+                    .attr("markerHeight", 12)
+                    .attr("orient", "auto")
+                    .append("path")
+                    .attr("d", "M 0,0 m -5,-5 L 5,0 L -5,5 Z")
+                    .attr('fill', function(d){
+                        return self.type_property_link[d].color;
+                    });
+        }
+
+        this.customBehavioursOnEvents = args.behaviorsOnEvents || undefined;
+
+        var self = this;
+        var data_url = (args.data_url) ? args.data_url : "graph_data/";
+        if (!args.graph_data) {
+            d3.json(data_url, function (error, data) {
+                //console.log(JSON.stringify(data))
+                self.d3_graph.nodes = data.vertices;
+                self.d3_graph.links = data.edges;
+                self.d3_graph.graph_parameters = data.graph_parameters;
+                self.model = data.model;
+                self.refreshGraphParameters(self.d3_graph.graph_parameters);
+                self.refresh();
+                self.startForce();
+                //if(args.filter_base != undefined)
+
+                setTimeout(function () {
+                    //self.handleForce(self.forceSimulationActive);
+                    //var f_t = {"node":{"type":[],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":[""]}}
+                    //var f_t ={"node":{"type":["vnf_vl","vnf_ext_cp","vnf_vdu_cp","vnf_vdu","vnf_click_vdu"],"group":["vlan_r3u0"]},"link":{"group":["vlan_r3u0"],"view":["vnf"]}}
+                    self.handleFiltersParams(args.filter_base);
+                    //self.handleFiltersParams(f_t);
+                    //console.log(JSON.stringify(args.filter_base))
+                    //console.log(self.d3_graph.nodes.length)
+                    //console.log(JSON.stringify(self.d3_graph.nodes))
+                    //self.d3_graph.nodes.forEach(function(key, index){
+                    //console.log(key, index);
+                    //})
+                }, 500);
+
+            });
+        } else {
+            this.updateData(args)
+        }
+    }
+
+    /**
+     * Update data of the graph.
+     * @param {Object} Required. An object that specifies tha data of the new node.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.updateData = function (args) {
+        console.log("updateData")
+        this.d3_graph.nodes = args.graph_data.vertices;
+        this.d3_graph.links = args.graph_data.edges;
+        this.d3_graph.graph_parameters = args.graph_parameters;
+        this.model = args.model;
+        this.refreshGraphParameters(this.d3_graph.graph_parameters);
+        this.refresh();
+        this.startForce();
+        //if(args.filter_base != undefined)
+
+        //if(args.filter_base){
+        var self = this;
+        setTimeout(function () {
+            self.handleForce(true);
+            self.handleFiltersParams(args.filter_base);
+        }, 500);
+        //}
+    }
+
+    /**
+     * Add a new node to the graph.
+     * @param {Object} Required. An object that specifies tha data of the new node.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.addNode = function (node, success, error) {
+        var self = this;
+        var current_layer = self.getCurrentView();
+        var node_type = node.info.type;
+
+        if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].addable) {
+            if (self.model.layer[current_layer].nodes[node_type].addable.callback) {
+                var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].addable.callback].class;
+                var controller = new dreamer[c]();
+                controller[self.model.layer[current_layer].nodes[node_type].addable.callback](self, node, function () {
+                    self.parent.addNode.call(self, node);
+                    success && success();
+                }, error);
+
+            } else {
+
+                log('addNode: callback undefined in model spec.');
+                error && error("You can't add a " + node.info.type + ", callback undefined.");
+            }
+        } else {
+            //FIXME Error handling????
+            log("You can't add a " + node.info.type + " in a current layer " + current_layer);
+            error && error("You can't add a " + node.info.type + " in a current layer " + current_layer);
+        }
+    };
+
+
+
+    /**
+     * Update the data properties of the node
+     * @param {Object} Required. An object that specifies tha data of the node.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.updateDataNode = function (args) {
+        //FIXME updating a node properties need commit to server side!
+        this.parent.updateDataNode.call(this, args);
+    };
+
+    /**
+     * Remove a node from graph and related links.
+     * @param {String} Required. Id of node to remove.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.removeNode = function (node, success, error) {
+        console.log('removeNode', JSON.stringify(node))
+        var self = this;
+        var current_layer = self.getCurrentView();
+        var node_type = node.info.type;
+        if (node.info.desc_id == undefined){
+            node.info.desc_id = self.desc_id;
+        }
+        if (self.model.layer[current_layer] && self.model.layer[current_layer].nodes[node_type] && self.model.layer[current_layer].nodes[node_type].removable) {
+            if (self.model.layer[current_layer].nodes[node_type].removable.callback) {
+                var c = self.model.callback[self.model.layer[current_layer].nodes[node_type].removable.callback].class;
+                var controller = new dreamer[c]();
+                controller[self.model.layer[current_layer].nodes[node_type].removable.callback](self, node, function () {
+                    self.parent.removeNode.call(self, node);
+                    success && success();
+                }, error);
+            } else {
+
+                log('removeNode: callback undefined in model spec.');
+                error && error("You can't remove a " + node.info.type + ", callback undefined.");
+            }
+        } else {
+            //FIXME we need to manage alert in a different way: FAILBACK
+            log("You can't remove a " + node.info.type);
+            error && error("You can't remove a " + node.info.type);
+        }
+    };
+
+    /**
+     * Add a new link to graph.
+     * @param {Object} Required. An object that specifies tha data of the new Link.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.addLink = function (s, d, success, error) {
+        var self = this;
+        var source_id = s.id;
+        var target_id = d.id;
+        var source_type = s.info.type;
+        var destination_type = d.info.type;
+        var link = {
+            source: s,
+            target: d,
+            view: this.filter_parameters.link.view[0],
+            group: this.filter_parameters.link.group,
+            desc_id: this.desc_id
+        };
+        log("addLink: " + JSON.stringify(link))
+        var current_layer = self.getCurrentView()
+        if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]) {
+
+            if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback) {
+                var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].callback;
+                console.log(callback, self.model.callback)
+                var direct_edge = 'direct_edge' in self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] ? self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type]['direct_edge'] : false;
+                link.directed_edge = direct_edge;
+                var c = self.model.callback[callback].class;
+                var controller = new dreamer[c]();
+                controller[callback](self, link, function () {
+                    self._deselectAllNodes();
+                    self.parent.addLink.call(self, link);
+                    if (success)
+                        success();
+                }, error);
+            } else {
+                log('addLink: callback undefined in model spec.');
+                error && error("You can't add a link, callback undefined.");
+            }
+
+        } else {
+            //FIXME we need to manage alert in a different way: FAILBACK
+            log("You can't link a " + source_type + " with a " + destination_type);
+
+            error && error("You can't link a " + source_type + " with a " + destination_type);
+        }
+    };
+
+    /**
+     * Remove a link from graph.
+     * @param {String} Required. The identifier of link to remove.
+     * @returns {boolean}
+     */
+    ModelGraphEditor.prototype.removeLink = function (link, success, error) {
+        var self = this;
+        var s = link.source;
+        var d = link.target;
+        var source_type = s.info.type;
+        var destination_type = d.info.type;
+        var current_layer = self.getCurrentView()
+        if (self.model.layer[current_layer].allowed_edges && self.model.layer[current_layer].allowed_edges[source_type] && self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type] &&
+            self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable
+        ) {
+            if (self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback) {
+                var callback = self.model.layer[current_layer].allowed_edges[source_type].destination[destination_type].removable.callback;
+                var c = self.model.callback[callback].class;
+                var controller = new dreamer[c]();
+                controller[callback](self, link, function () {
+                    self._deselectAllNodes();
+                    self._deselectAllLinks();
+                    self.parent.removeLink.call(self, link.index);
+                    success && success();
+                }, error);
+            } else {
+                log('removeLink: callback undefined in model spec.');
+                error && error("You can't remove a link, callback undefined.");
+            }
+
+        } else {
+            //FIXME we need to manage alert in a different way: FAILBACK
+            log("You can't delete the link");
+            error && error("You can't delete the link");
+        }
+
+
+    };
+
+
+    ModelGraphEditor.prototype.savePositions = function (data) {
+        var vertices = {}
+        this.node.each(function (d) {
+            vertices[d.id] = {}
+            vertices[d.id]['x'] = d.x;
+            vertices[d.id]['y'] = d.y;
+        });
+        new dreamer.GraphRequests().savePositions({
+            'vertices': vertices
+        });
+
+    }
+
+    /**
+     *  Internal functions
+     */
+
+    /**
+     *
+     *
+     */
+
+    ModelGraphEditor.prototype._setupBehaviorsOnEvents = function (layer) {
+
+        var self = this;
+        var contextMenuLinksAction = [{
+            title: 'Delete Link',
+            action: function (elm, link, i) {
+                self.removeLink(link, null, showAlert);
+            },
+            edit_mode: true
+        }];
+        var contextMenuNodesAction = [{
+                title: 'Edit',
+                action: function (elm, d, i) {
+                    if (d.info.type != undefined) {
+                        self.eventHandler.fire("edit_descriptor", self.project_id, d);
+                    }
+                },
+                nodes: [],
+                edit_mode: true
+            },
+            {
+                title: 'Delete',
+                action: function (elm, d, i) {
+                    self.removeNode(d, null, showAlert);
+                },
+                edit_mode: true
+            },
+
+        ];
+        if(this.customBehavioursOnEvents){
+            contextMenuNodesAction = contextMenuNodesAction.concat(this.customBehavioursOnEvents['behaviors'].nodes);
+        }
+
+
+        if ( self.model && self.model.layer && self.model.layer[layer] && self.model.layer[layer].action && self.model.layer[layer].action.node) {
+            for (var i in self.model.layer[layer].action.node) {
+                var action = self.model.layer[layer].action.node[i]
+                contextMenuNodesAction.push({
+                    title: action.title,
+                    action: function (elm, d, i) {
+                        var callback = action.callback;
+                        var c = self.model.callback[callback].class;
+                        var controller = new dreamer[c]();
+                        var args = {
+                            elm: elm,
+                            d: d,
+                            i: i
+                        }
+
+                        controller[callback](self, args);
+                    },
+                    edit_mode: (action.edit_mode != undefined) ? action.edit_mode: undefined
+                });
+            }
+        }
+
+        this.behavioursOnEvents = {
+            'nodes': {
+                'click': function (d) {
+
+                    d3.event.preventDefault();
+
+                    if (self._edit_mode && self.lastKeyDown == SHIFT_BUTTON && self._selected_node != undefined) {
+                        self.addLink(self._selected_node, d, null, showAlert);
+                    } else {
+                        self._selectNodeExclusive(this, d);
+                    }
+
+                },
+                'mouseover': function (d) {
+                    self.link.style('stroke-width', function (l) {
+                        if (d === l.source || d === l.target)
+                            return 4;
+                        else
+                            return 2;
+                    });
+                },
+                'mouseout': function (d) {
+                    self.link.style('stroke-width', 2);
+                },
+                'contextmenu': d3.contextMenu(contextMenuNodesAction, {
+                    'edit_mode': self._edit_mode,
+                    'layer': layer,
+                    'type_object': 'node'
+                })
+            },
+            'links': {
+                'click': function (d) {
+                    self._selectLinkExclusive(this, d);
+
+                },
+                'dblclick': function (event) {
+
+                },
+                'mouseover': function (d) {
+                    d3.select(this).style('stroke-width', 4);
+                },
+                'mouseout': function (d) {
+                    if (d != self._selected_link)
+                        d3.select(this).style('stroke-width', 2);
+                },
+                'contextmenu': d3.contextMenu(contextMenuLinksAction, {
+                    'edit_mode': self._edit_mode,
+                    'layer': layer,
+                    'type_object': 'link'
+                })
+            }
+        }
+    };
+
+    ModelGraphEditor.prototype.handleFiltersParams = function (filtersParams, notFireEvent) {
+
+        this.parent.handleFiltersParams.call(this, filtersParams, notFireEvent);
+        this._setupBehaviorsOnEvents(filtersParams.link.view[0]);
+    };
+
+    ModelGraphEditor.prototype.getAvailableNodes = function () {
+        log('getAvailableNodes');
+        log(this.model)
+        if (this.model && this.model.layer[this.getCurrentView()] != undefined)
+            return this.model.layer[this.getCurrentView()].nodes;
+        return [];
+    }
+
+
+    ModelGraphEditor.prototype.exploreLayer = function (args) {
+
+    };
+
+    ModelGraphEditor.prototype.getTypeProperty = function () {
+        return this.type_property;
+    };
+
+    ModelGraphEditor.prototype.getCurrentGroup = function () {
+        return this.filter_parameters.node.group[0];
+
+    }
+
+    ModelGraphEditor.prototype.getCurrentView = function () {
+        return this.filter_parameters.link.view[0];
+
+    }
+    /**
+     * Log utility
+     */
+    function log(text) {
+        if (DEBUG)
+            console.log("::ModelGraphEditor::", text);
+    }
+
+
+
+    return ModelGraphEditor;
+
+
+}(this));
+
+if (typeof module === 'object') {
+    module.exports = dreamer.ModelGraphEditor;
+}
\ No newline at end of file
diff --git a/template/base.html b/template/base.html
new file mode 100644 (file)
index 0000000..60576d1
--- /dev/null
@@ -0,0 +1,159 @@
+{% load staticfiles %}
+<!DOCTYPE html>
+<html>
+<head>
+
+    {% block head_base %}
+        {% include "head.html" %}
+    {% endblock %}
+    {% block head_block %}
+
+    {% endblock %}
+  <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+  <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+  <!--[if lt IE 9]>
+  <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
+  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+  <![endif]-->
+
+  <!-- Google Font
+  <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">-->
+</head>
+
+
+{% block body %}
+
+{% if request.COOKIES.collapsed_sidebar == '1' %}
+<body class="skin-purple sidebar-mini sidebar-collapse">
+{% else %}
+<body class="skin-purple hold-transition sidebar-mini">
+{% endif %}
+
+<div class="wrapper">
+
+  <header class="main-header">
+    <!-- Logo -->
+    {% block logo_sidebar %}
+        {% include "logo_sidebar.html" %}
+    {% endblock %}
+    <!-- Header Navbar: style can be found in header.less -->
+    <nav class="navbar navbar-static-top">
+      <!-- Sidebar toggle button-->
+      <a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
+        <span class="sr-only">Toggle navigation</span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+        <span class="icon-bar"></span>
+      </a>
+
+      <div class="navbar-custom-menu">
+        <ul class="nav navbar-nav">
+            {% if user.get_full_name %}
+          <!-- User Account: style can be found in dropdown.less -->
+          <li class="dropdown user user-menu">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
+              <img src="{% static  user.get_avatar  %}" class="user-image" alt="User Image">
+                        <span class="hidden-xs">{{ user.username }}</span>
+            </a>
+            <ul class="dropdown-menu">
+                        <!-- User image -->
+                        <li class="user-header">
+                            <img src="{% static  user.get_avatar  %}" class="img-circle" alt="User Image">
+                            <p id="user_menu_name_role">
+                                {{ user.get_full_name }}
+                                <small id="user_menu_role">{{ user.get_user_role_name }}</small>
+                            </p>
+                        </li>
+                        <!-- Menu Footer-->
+                        <li class="user-footer">
+                            <div class="pull-left">
+                              <a href="#" class="btn btn-default btn-flat" disabled>Settings</a>
+                            </div>
+                            <div class="pull-right">
+                                <a href="/auth" class="btn btn-default btn-flat">Sign out</a>
+                            </div>
+                        </li>
+                    </ul>
+          </li>
+            {% endif %}
+        </ul>
+      </div>
+    </nav>
+  </header>
+
+        <!-- =============================================== -->
+        {% block left_sidebar %}
+
+        {% endblock %}
+
+      <!-- =============================================== -->
+
+      <!-- Content Wrapper. Contains page content -->
+      <div class="content-wrapper">
+          <!-- Content Header (Page header) -->
+            <section class="content-header">
+                <h1 id="title_header">
+                    {% block title_header_big %}
+
+                    {% endblock %}
+                    <small>
+                        {% block title_header_small %}
+
+                        {% endblock %}
+                    </small>
+                </h1>
+                <ol id="breadcrumb" class="breadcrumb">
+                    {% block breadcrumb_body %}
+                        <li><a href='{% url "home" %}'><i class="fa fa-home fa-fw"></i> Home</a></li>
+                    {% endblock %}
+                </ol>
+            </section>
+
+          <!-- Main content -->
+            <section id="main_content" class="content">
+                {% block content_body %}
+
+                {% endblock %}
+            </section>
+          <!-- /.content -->
+        {% include "modals/error_alert.html" %}
+      </div><!-- /.content-wrapper -->
+        {% block footer %}
+            {% include "footer.html" %}
+        {% endblock %}
+
+
+    </div>
+<!-- ./wrapper -->
+
+<!-- jQuery 3 -->
+<script src="/static/bower_components/jquery/dist/jquery.min.js"></script>
+<!-- Bootstrap 3.3.7 -->
+<script src="/static/bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
+<!-- FastClick -->
+<script src="/static/bower_components/fastclick/lib/fastclick.js"></script>
+<!-- AdminLTE App -->
+<script src="/static/bower_components/admin-lte/dist/js/adminlte.min.js"></script>
+<!-- iCheck -->
+<script src="/static/bower_components/admin-lte/plugins/iCheck/icheck.min.js"></script>
+<!-- pace -->
+<script src="/static/bower_components/admin-lte/plugins/pace/pace.min.js"></script>
+<!-- bootbox -->
+<script src="/static/bower_components/bootbox.js/bootbox.js"></script>
+<!-- RDCL3D AdminLTE session storage handler -->
+<script src="/static/src/adminlte_session_storage.js"></script>
+<!-- Utility JS -->
+<script src="/static/src/utils.js"></script>
+
+<script>
+    $(document).ready(function () {
+        $('[data-toggle="tooltip"]').tooltip()
+    });
+
+</script>
+{% block resource_block %}
+
+{% endblock %}
+</body>
+{% endblock %}
+</html>
diff --git a/template/error.html b/template/error.html
new file mode 100644 (file)
index 0000000..a55d97b
--- /dev/null
@@ -0,0 +1,28 @@
+{% extends 'base.html' %}
+
+{% block title_header_big %}
+    {{ block.super }}
+    Error Page
+{% endblock %}
+
+{% block content_body %}
+<div class="error-page">
+        <h2 class="headline text-yellow"> Error</h2>
+
+        <div class="error-content">
+          <h3><i class="fa fa-warning text-yellow"></i> {{error_message}}</h3>
+
+          <p>
+            Meanwhile, you may <a href='{% url "home" %}'>return to dashboard</a> .
+          </p>
+
+        </div>
+        <!-- /.error-content -->
+      </div>
+{% endblock %}
+
+{% block resource_block %}
+
+{% endblock %}
+
+
diff --git a/template/footer.html b/template/footer.html
new file mode 100644 (file)
index 0000000..e29c9ad
--- /dev/null
@@ -0,0 +1,12 @@
+{% load staticfiles %}
+{% load i18n %}
+
+<footer class="main-footer">
+    <div class="pull-right hidden-xs">
+        <strong>{{ SITE_NAME }}</strong>
+    </div>
+
+    <strong><a href="https://osm.etsi.org">OSM</a></strong>
+
+
+</footer>
diff --git a/template/forbidden.html b/template/forbidden.html
new file mode 100644 (file)
index 0000000..4a798c2
--- /dev/null
@@ -0,0 +1,28 @@
+{% extends 'base.html' %}
+
+{% block title_header_big %}
+    {{ block.super }}
+    Error Page
+{% endblock %}
+
+{% block content_body %}
+<div class="error-page">
+        <h2 class="headline text-yellow"> Error 403</h2>
+
+        <div class="error-content">
+          <h3><i class="fa fa-warning text-yellow"></i> {{error_message}}</h3>
+
+          <p>
+            Meanwhile, you may <a href='{% url "home" %}'>return to dashboard</a> .
+          </p>
+
+        </div>
+        <!-- /.error-content -->
+      </div>
+{% endblock %}
+
+{% block resource_block %}
+
+{% endblock %}
+
+
diff --git a/template/head.html b/template/head.html
new file mode 100644 (file)
index 0000000..44ae00c
--- /dev/null
@@ -0,0 +1,26 @@
+{% load staticfiles %}
+{% load i18n %}
+
+  <title>{{ SITE_NAME }}</title>
+
+  <!-- Tell the browser to be responsive to screen width -->
+  <meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
+  <!-- Bootstrap 3.3.7 -->
+  <link rel="stylesheet" href="/static/bower_components/bootstrap/dist/css/bootstrap.min.css">
+  <!-- Font Awesome -->
+  <link rel="stylesheet" href="/static/bower_components/font-awesome/css/font-awesome.min.css">
+  <!-- Ionicons -->
+  <link rel="stylesheet" href="/static/bower_components/Ionicons/css/ionicons.min.css">
+  <!-- Theme style -->
+  <link rel="stylesheet" href="/static/bower_components/admin-lte/dist/css/AdminLTE.min.css">
+  <!-- AdminLTE Skins. Choose a skin from the css/skins
+       folder instead of downloading all of them to reduce the load. -->
+  <link rel="stylesheet" href="/static/bower_components/admin-lte/dist/css/skins/_all-skins.min.css">
+
+  <link rel="stylesheet" href="/static/css/rdcl.css">
+
+  <link rel="stylesheet" href="/static/bower_components/admin-lte/plugins/pace/pace.min.css">
+  <link rel="icon" href="/static/assets/img/favicon.ico">
+
+
+
diff --git a/template/home.html b/template/home.html
new file mode 100644 (file)
index 0000000..ac1b2f4
--- /dev/null
@@ -0,0 +1,73 @@
+{% extends "base.html" %}
+
+{% load staticfiles %}
+
+{% block head_block %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block left_sidebar %}
+    {% include 'left_sidebar_base.html' %}
+{% endblock %}
+
+{% block breadcrumb_body %}
+       {{ block.super }}
+
+{% endblock %}
+
+{% block content_body %}
+       {{ block.super }}
+
+    <div class="row">
+    <div class="col-md-5 ">
+
+        <div class="small-box bg-purple">
+            <div class="inner">
+                <h3>{{ projects }}</h3>
+                <h3>Projects</h3>
+                <p></p>
+            </div>
+            <div class="icon">
+                <i class="fa fa-code"></i>
+            </div>
+            <a href="/projects/list" class="small-box-footer">
+                Open list <i class="fa fa-arrow-circle-right"></i>
+            </a>
+        </div>
+    </div>
+    <!--
+    <div class="col-md-6 ">
+        <div class="small-box bg-aqua">
+            <div class="inner">
+                <h3>{{deployments}}</h3>
+                <h3>Deployments</h3>
+                <p></p>
+            </div>
+            <div class="icon">
+                <i class="fa fa-terminal"></i>
+            </div>
+            {% if user.is_guest %}
+            <a href="#" class="small-box-footer">
+                Guest Users don't have permission <i class="fa fa-arrow-circle-right"></i>
+            </a>
+            {% else %}
+            <a href="/deployments/list" class="small-box-footer">
+                Open list <i class="fa fa-arrow-circle-right"></i>
+            </a>
+            {% endif %}
+        </div>
+    </div>
+-->
+</div>
+<div class="row">
+    <img style="display:block; margin:0 auto; width: 60%;" class="pad" src="/static/assets/img/osm_logo.svg" alt="Photo">
+</div>
+{% endblock %}
+
+{% block resource_block %}
+       {{ block.super }}
+
+
+
+{% endblock %}
diff --git a/template/left_sidebar_base.html b/template/left_sidebar_base.html
new file mode 100644 (file)
index 0000000..2ed2e2a
--- /dev/null
@@ -0,0 +1,23 @@
+<!-- Left side column. contains the sidebar -->
+<aside class="main-sidebar" id="globalmenu">
+    <!-- sidebar: style can be found in sidebar.less -->
+    <section class="sidebar">
+        <!-- sidebar menu: : style can be found in sidebar.less -->
+        <ul class="sidebar-menu">
+            <li class="header">MAIN NAVIGATION</li>
+            {% url "home" as  home_url %}
+            <li {% if request.get_full_path == home_url %} class="active" {% endif %} ><a href='{% url "home" %}'><i class="fa fa-home fa-fw"></i><span>Home</span></a></li>
+            {% url "projects:projects_list" as  projects_list_url %}
+            <li {% if request.get_full_path == projects_list_url %} class="active" {% endif %}><a href='{% url "projects:projects_list" %}'><i class="fa fa-code fa-fw"></i><span>Projects</span></a></li>
+            {% url "vim:list" as  vims_list_url %}
+            <li {% if request.get_full_path == vims_list_url %} class="active" {% endif %}><a href='{% url "vim:list" %}'><i class="fa fa-th-list fa-fw"></i><span>VIM Accounts</span></a></li>
+
+
+
+            {% block left_sidebar_base %}
+
+            {% endblock %}
+        </ul>
+    </section>
+    <!-- /.sidebar -->
+</aside>
diff --git a/template/login.html b/template/login.html
new file mode 100644 (file)
index 0000000..2323200
--- /dev/null
@@ -0,0 +1,51 @@
+{% extends 'base.html' %}
+
+{% block content_body %}
+<div class="login-box">
+    <div class="login-logo">
+        <a href='{% url "home" %}'><b>{{ SITE_NAME }} </b>Login</a>
+    </div>
+    <!-- /.login-logo -->
+    <div class="login-box-body">
+        <p class="login-box-msg">Sign in to start your session</p>
+
+        <form action="../auth/" method="post" onsubmit="">
+            <input id="next_input" type="hidden" name="next" class="form-control">
+            {% csrf_token %}
+            <div class="form-group has-feedback">
+                <input type="username" name="username" class="form-control" placeholder="Username">
+                <span class="glyphicon glyphicon-user form-control-feedback"></span>
+            </div>
+            <div class="form-group has-feedback">
+                <input type="password" name="password" class="form-control" placeholder="Password">
+                <span class="glyphicon glyphicon-lock form-control-feedback"></span>
+            </div>
+            <div class="row">
+                <div class="col-xs-8">
+
+                </div>
+                <!-- /.col -->
+                <div class="col-xs-4">
+                    <button type="submit" class="btn btn-primary btn-block btn-flat">Sign In</button>
+                </div>
+                <!-- /.col -->
+            </div>
+        </form>
+
+
+    </div>
+    <!-- /.login-box-body -->
+</div>
+<!-- /.login-box -->
+{% endblock %}
+
+{% block resource_block %}
+<script>
+    $(document).ready(function () {
+        var next_value = getUrlParameter('next');
+        console.log('next_value', next_value);
+        if(next_value)
+            $('#next_input').val(next_value);
+    });
+</script>
+{% endblock %}
diff --git a/template/logo_sidebar.html b/template/logo_sidebar.html
new file mode 100644 (file)
index 0000000..916bef9
--- /dev/null
@@ -0,0 +1,8 @@
+{% load staticfiles %}
+{% load i18n %}
+<a href="#" class="logo" style="background-color: white !important;">
+        <!-- mini logo for sidebar mini 50x50 pixels -->
+        <span class="logo-mini"><img src="{% static logo_mini|default:"assets/img/osm_small_logo.png" %}" style="height: 40px;margin-top: -10px;"></span>
+        <!-- logo for regular state and mobile devices -->
+        <span class="logo-lg"><img src="{% static logo|default:"assets/img/OSM-logo.png" %}" style="height: 40px;margin-top: -10px;"></span>
+    </a>
diff --git a/template/modals/error_alert.html b/template/modals/error_alert.html
new file mode 100644 (file)
index 0000000..89a8ed4
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="modal" id="modal_alert" xmlns="http://www.w3.org/1999/html">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" >Warning</h4>
+            </div>
+            <div  class="modal-body">
+                <label id="modal_alert_text"> </label>
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-primary pull-right" data-dismiss="modal">Ok</button>
+            </div>
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
\ No newline at end of file
diff --git a/template/topology_toolbar.html b/template/topology_toolbar.html
new file mode 100644 (file)
index 0000000..c5f8baa
--- /dev/null
@@ -0,0 +1,38 @@
+<div class="btn-group-vertical left-tool-bar">
+
+    <!-- <button id="topology_zoom_plus" type="button" class="btn btn-default"><i class="fa fa-plus"></i></button> -->
+    <!-- <button id="topology_zoom_minus" type="button" class="btn btn-default"><i class="fa fa-minus"></i></button> -->
+
+    <button id="topology_play" type="button" title="Play force" class="btn btn btn-default" onclick="handleForce(this)"><i
+            class="fa fa-play"></i></button>
+    <button id="topology_pause" type="button" title="Stop force" class="btn btn btn-default active" onclick="handleForce(this)"><i
+            class="fa fa-pause"></i></button>
+    {% block topology_toolbar_buttons %}
+
+    {% endblock %}
+
+    <!-- <button id="topology_settings" type="button" class="btn btn-default"><i class="fa fa-gear"></i></button> -->
+
+    <button id="topology_open_editor" type="button" title="Edit descriptor" class="btn btn-default" onclick="openEditor({{project_id}})"><i
+            class="fa fa-edit"></i></button>
+
+    <button id="topology_save" type="button" title="Save positions" class="btn btn-default" onclick="savePositions(this)"><i
+            class="fa fa-save"></i></button>
+
+    <button id="topology_info_button" type="button" title="Info keyboard" class="btn btn-default" data-toggle="modal"
+            data-target="#modalTopologyInfoButton"><i class="fa fa-keyboard-o"></i></button>
+</div>
+{% block boxes_toolbar %}
+
+{% endblock %}
+
+
+
+<div id="palette" >
+    <div id="palette_status_indicator" class="palette-status-indicator">
+        <i class="fa fa-refresh fa-spin fa-3x fa-fw" aria-hidden="true"></i>
+    </div>
+    <div id="paletteContainer" class="palette-container">
+
+    </div>
+</div>
diff --git a/vimhandler/__init__.py b/vimhandler/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/vimhandler/admin.py b/vimhandler/admin.py
new file mode 100644 (file)
index 0000000..2e9690a
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.contrib import admin
+
+# Register your models here.
diff --git a/vimhandler/apps.py b/vimhandler/apps.py
new file mode 100644 (file)
index 0000000..db0dbdf
--- /dev/null
@@ -0,0 +1,23 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.apps import AppConfig
+
+
+class VimhandlerConfig(AppConfig):
+    name = 'vimhandler'
diff --git a/vimhandler/models.py b/vimhandler/models.py
new file mode 100644 (file)
index 0000000..21d5735
--- /dev/null
@@ -0,0 +1,21 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 unicode_literals
+
+from django.db import models
+
+# Create your models here.
diff --git a/vimhandler/template/config/aws.html b/vimhandler/template/config/aws.html
new file mode 100644 (file)
index 0000000..911e927
--- /dev/null
@@ -0,0 +1,79 @@
+<div class="panel" id="aws_config" style="display: none;">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#aws_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="aws_config_collapse" class="panel-collapse collapse ">
+        <div class="col-lg-6">
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_sdn_ctrl" name="config_sdn_controller"
+                               placeholder="sdn_controller">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_sdn_port_map" name="config_sdn_port_mapping"
+                               placeholder="sdn_port_mapping">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_region_name" class="col-sm-4 control-label">Region name</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_region_name" name="config_region_name"
+                               placeholder="region_name">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vpc_cidr_block" class="col-sm-4 control-label">VPC cidr block</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vpc_cidr_block" name="config_vpc_cidr_block"
+                               placeholder="vpc_cidr_block">
+                    </div>
+                </div>
+            </div>
+
+        </div>
+
+        <div class="col-lg-6">
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_security_groups" class="col-sm-4 control-label">Security groups</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_security_groups" name="config_security_groups"
+                               placeholder="security_groups">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_key_pair" class="col-sm-4 control-label">Key pair</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_key_pair" name="config_key_pair"
+                               placeholder="key_pair">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="aws_flavor_info" class="col-sm-4 control-label">Flavor info</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="aws_flavor_info" name="config_flavor_info"
+                               placeholder="flavor_info">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
diff --git a/vimhandler/template/config/aws_show.html b/vimhandler/template/config/aws_show.html
new file mode 100644 (file)
index 0000000..428294a
--- /dev/null
@@ -0,0 +1,58 @@
+<div class="panel" id="aws_config">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#aws_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="aws_config_collapse" class="panel-collapse collapse ">
+        <div class="col-lg-6">
+            <div class="row">
+                    <label for="aws_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_sdn_ctrl"> {{datacenter.sdn_ctrl||default:"---" }}</p>
+                    </div>
+            </div>
+            <div class="row">
+                    <label for="aws_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_sdn_port_map"> {{datacenter.sdn_port_map||default:"---" }}</p>
+                    </div>
+            </div>
+            <div class="row">
+                    <label for="aws_region_name" class="col-sm-4 control-label">Region name</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_region_name"> {{datacenter.region_name||default:"---" }}</p>
+                    </div>
+            </div>
+            <div class="row">
+                    <label for="vpc_cidr_block" class="col-sm-4 control-label">VPC cidr block</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="vpc_cidr_block"> {{datacenter.vpc_cidr_block||default:"---" }}</p>
+                    </div>
+            </div>
+
+        </div>
+
+        <div class="col-lg-6">
+            <div class="row">
+                    <label for="aws_security_groups" class="col-sm-4 control-label">Security groups</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_security_groups"> {{datacenter.security_groups||default:"---" }}</p>
+                    </div>
+            </div>
+            <div class="row">
+                    <label for="aws_key_pair" class="col-sm-4 control-label">Key pair</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_key_pair"> {{datacenter.key_pair||default:"---" }}</p>
+                    </div>
+            </div>
+            <div class="row">
+                    <label for="aws_flavor_info" class="col-sm-4 control-label">Flavor info</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <p id="aws_flavor_info"> {{datacenter.flavor_info||default:"---" }}</p>
+                    </div>
+            </div>
+        </div>
+    </div>
+</div>
+
diff --git a/vimhandler/template/config/openstack.html b/vimhandler/template/config/openstack.html
new file mode 100644 (file)
index 0000000..63923a0
--- /dev/null
@@ -0,0 +1,198 @@
+<div class="panel" id="openstack_config" style="display: none;">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#openstack_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="openstack_config_collapse" class="panel-collapse collapse ">
+
+    <div class="col-lg-6">
+
+        <div class="row">
+            <div class="form-group">
+                <label for="ostack_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ostack_sdn_ctrl" name="config_sdn_controller"
+                           placeholder="sdn_controller">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="ostack_ssdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ostack_ssdn_port_map" name="config_sdn_port_mapping"
+                           placeholder="sdn_port_mapping">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="ostack_vim_type" class="col-sm-4 control-label">VIM type</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ostack_vim_type" name="config_vim_type"
+                           placeholder="vim_type">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="ostack_security_groups" class="col-sm-4 control-label">Security Groups</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ostack_security_groups" name="config_security_groups"
+                           placeholder="security_groups">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="availability_zone" class="col-sm-4 control-label">Availability Zone</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="availability_zone" name="config_availability_zone"
+                           placeholder="availability_zone">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="ostack_region_name" class="col-sm-4 control-label">Region Name</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ostack_region_name" name="config_region_name"
+                           placeholder="region_name">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="option_insecure" class="col-sm-4 control-label">Insecure</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <select id="option_insecure" name="config_insecure" class="form-control">
+                        <option value="" disabled selected>None</option>
+                        <option value=true>True</option>
+                        <option value=false>False</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="use_existing_flavors" class="col-sm-4 control-label">Use existing flavors</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <select id="use_existing_flavors" name="config_use_existing_flavors" class="form-control">
+                        <option value="" disabled selected>None</option>
+                        <option value=true>True</option>
+                        <option value=false>False</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="use_internal_endpoint" class="col-sm-4 control-label">Use internal endpoint</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <select id="use_internal_endpoint" name="config_use_internal_endpoint" class="form-control">
+                        <option value="" disabled selected>None</option>
+                        <option value=true>True</option>
+                        <option value=false>False</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="APIversion" class="col-sm-4 control-label">API version</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="APIversion" name="config_APIversion" placeholder="API version">
+                </div>
+            </div>
+        </div>
+
+    </div>
+    <div class="col-lg-6">
+        <div class="row">
+            <div class="form-group">
+                <label for="project_domain_id" class="col-sm-4 control-label">Project domain id</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="project_domain_id" name="config_project_domain_id"
+                           placeholder="project_domain_id">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="project_domain_name" class="col-sm-4 control-label">Project domain name</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="project_domain_name" name="config_project_domain_name"
+                           placeholder="project_domain_name">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="user_domain_id" class="col-sm-4 control-label">User domain id</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="user_domain_id" name="config_user_domain_id"
+                           placeholder="user_domain_id">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="user_domain_name" class="col-sm-4 control-label">User domain name</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="user_domain_name" name="config_user_domain_name"
+                           placeholder="user_domain_name">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="keypair" class="col-sm-4 control-label">Keypair</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="keypair" name="config_keypair" placeholder="Keypair">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="dataplane_physical_net" class="col-sm-4 control-label">Dataplane physical net</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="dataplane_physical_net" name="config_dataplane_physical_net"
+                           placeholder="dataplane_physical_net">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="use_floating_ip" class="col-sm-4 control-label">Use floating ip</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <select id="use_floating_ip" name="config_use_floating_ip" class="form-control">
+                        <option value="" disabled selected>None</option>
+                        <option value=true>True</option>
+                        <option value=false>False</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="dataplane_net_vlan_range" class="col-sm-4 control-label">Dataplane net vlan range</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="dataplane_net_vlan_range" name="config_dataplane_net_vlan_range"
+                           placeholder="dataplane_net_vlan_range">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="microversion" class="col-sm-4 control-label">Microversion</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="microversion" name="config_microversion"
+                           placeholder="Microversion">
+                </div>
+            </div>
+        </div>
+
+    </div>
+</div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/config/openstack_show.html b/vimhandler/template/config/openstack_show.html
new file mode 100644 (file)
index 0000000..7b39222
--- /dev/null
@@ -0,0 +1,130 @@
+<div class="panel" id="openstack_config">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#openstack_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="openstack_config_collapse" class="panel-collapse collapse ">
+
+        <div class="col-lg-6">
+
+            <div class="row">
+                <label for="ostack_sdn_ctrl" class="col-sm-4 control-label">SDN Controller:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ostack_sdn_ctrl"> {{datacenter.sdn_ctrl|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="ostack_ssdn_port_map" class="col-sm-4 control-label">SDN Port Mapping:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ostack_ssdn_port_map"> {{datacenter.sdn_port_map|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="ostack_security_groups" class="col-sm-4 control-label">Security Groups:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ostack_security_groups"> {{datacenter.security_groups|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="availability_zone" class="col-sm-4 control-label">Availability Zone:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="availability_zone"> {{datacenter.availability_zone|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="ostack_region_name" class="col-sm-4 control-label">Region Name:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ostack_region_name"> {{datacenter.region_name|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="option_insecure" class="col-sm-4 control-label">Insecure:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="option_insecure"> {{datacenter.insecure|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="use_existing_flavors" class="col-sm-4 control-label">Use existing flavors:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="use_existing_flavors"> {{datacenter.use_existing_flavors|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="use_internal_endpoint" class="col-sm-4 control-label">Use internal endpoint:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="use_internal_endpoint"> {{datacenter.use_internal_endpoint|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="APIversion" class="col-sm-4 control-label">API version:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="APIversion"> {{datacenter.APIversion|default:"---" }}</p>
+                </div>
+            </div>
+
+        </div>
+        <div class="col-lg-6">
+            <div class="row">
+
+                <label for="project_domain_id" class="col-sm-4 control-label">Project domain id:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="project_domain_id"> {{datacenter.project_domain_id|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="project_domain_name" class="col-sm-4 control-label">Project domain name:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="project_domain_name"> {{datacenter.project_domain_name|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="user_domain_id" class="col-sm-4 control-label">User domain id:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="user_domain_id"> {{datacenter.user_domain_id|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+                <label for="user_domain_name" class="col-sm-4 control-label">User domain name:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="user_domain_name"> {{datacenter.user_domain_name|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="keypair" class="col-sm-4 control-label">Keypair:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="keypair"> {{datacenter.keypair|default:"---" }}</p></div>
+            </div>
+            <div class="row">
+
+                <label for="dataplane_physical_net" class="col-sm-4 control-label">Dataplane physical net:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="dataplane_physical_net"> {{datacenter.dataplane_physical_net|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="use_floating_ip" class="col-sm-4 control-label">Use floating ip:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="use_floating_ip"> {{datacenter.use_floating_ip|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="dataplane_net_vlan_range" class="col-sm-4 control-label">Dataplane net vlan range:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="dataplane_net_vlan_range"> {{datacenter.dataplane_net_vlan_range|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="microversion" class="col-sm-4 control-label">Microversion:</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="microversion"> {{datacenter.microversion|default:"---" }}</p></div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/config/openvim.html b/vimhandler/template/config/openvim.html
new file mode 100644 (file)
index 0000000..1989665
--- /dev/null
@@ -0,0 +1,27 @@
+<div class="panel" id="openvim_config" style="display: none;">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#openvim_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="openvim_config_collapse" class="col-lg-6 panel-collapse collapse ">
+
+        <div class="row ">
+            <div class="form-group">
+                <label for="ov_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ov_sdn_ctrl" name="config_sdn_controller" placeholder="sdn_controller">
+                </div>
+            </div>
+        </div>
+        <div class="row">
+            <div class="form-group">
+                <label for="ov_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <input disabled class="form-control config-input" id="ov_sdn_port_map" name="config_sdn_port_mapping"
+                           placeholder="sdn_port_mapping">
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/config/openvim_show.html b/vimhandler/template/config/openvim_show.html
new file mode 100644 (file)
index 0000000..dbe57dd
--- /dev/null
@@ -0,0 +1,23 @@
+<div class="panel" id="openvim_config" >
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#openvim_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="openvim_config_collapse" class="col-lg-6 panel-collapse collapse ">
+
+        <div class="row ">
+            
+                <label for="ov_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ov_sdn_ctrl"> {{datacenter.sdn_ctrl|default:"---" }}</p>
+                </div>
+        </div>
+        <div class="row">
+                <label for="ov_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="ov_sdn_port_map"> {{datacenter.sdn_port_map|default:"---" }}</p>
+                </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/config/vmware.html b/vimhandler/template/config/vmware.html
new file mode 100644 (file)
index 0000000..8e8882f
--- /dev/null
@@ -0,0 +1,148 @@
+<div class="panel" id="vmware_config" style="display: none;">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#vmware_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="vmware_config_collapse" class="panel-collapse collapse ">
+        <div class="col-lg-6">
+            <div class="row">
+                <div class="form-group">
+                    <label for="vmware_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vmware_sdn_ctrl" name="config_sdn_controller"
+                               placeholder="sdn_controller">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vmware_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vmware_sdn_port_map" name="config_sdn_port_mapping"
+                               placeholder="sdn_port_mapping">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="orgname" class="col-sm-4 control-label">Org. name</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="orgname" name="config_orgname"
+                               placeholder="orgname">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="admin_username" class="col-sm-4 control-label">Admin username</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="admin_username" name="config_admin_username"
+                               placeholder="admin_username">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="admin_password" class="col-sm-4 control-label">Admin password</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" type="password" id="admin_password" name="config_admin_password"
+                               placeholder="admin_password">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="nsx_manager" class="col-sm-4 control-label">Nsx manager</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="nsx_manager" name="config_nsx_manager"
+                               placeholder="nsx_manager">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="nsx_user" class="col-sm-4 control-label">Nsx user</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="nsx_user" name="config_nsx_user"
+                               placeholder="nsx_user">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="nsx_password" class="col-sm-4 control-label">Nsx password</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled type="password" class="form-control config-input" id="nsx_password" name="config_nsx_password"
+                               placeholder="nsx_password">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="col-lg-6">
+            <div class="row">
+                <div class="form-group">
+                    <label for="vcenter_ip" class="col-sm-4 control-label">Vcenter ip</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vcenter_ip" name="config_vcenter_ip"
+                               placeholder="vcenter_ip">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vcenter_port" class="col-sm-4 control-label">Vcenter port</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vcenter_port" name="config_vcenter_port"
+                               placeholder="vcenter_port">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vcenter_user" class="col-sm-4 control-label">Vcenter user</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vcenter_user" name="config_vcenter_user"
+                               placeholder="vcenter_user">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vcenter_password" class="col-sm-4 control-label">Vcenter password</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled type="password" class="form-control config-input" id="vcenter_password" name="config_vcenter_password"
+                               placeholder="vcenter_password">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vrops_site" class="col-sm-4 control-label">Vrops site</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vrops_site" name="config_vrops_site"
+                               placeholder="vrops_site">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vrops_user" class="col-sm-4 control-label">Vrops user</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" id="vrops_user" name="config_vrops_user"
+                               placeholder="vrops_user">
+                    </div>
+                </div>
+            </div>
+            <div class="row">
+                <div class="form-group">
+                    <label for="vrops_password" class="col-sm-4 control-label">Vrops password</label>
+                    <div class="col-sm-6 input-group input-group-sm">
+                        <input disabled class="form-control config-input" type="password" id="vrops_password" name="config_vrops_password"
+                               placeholder="vrops_password">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/config/vmware_show.html b/vimhandler/template/config/vmware_show.html
new file mode 100644 (file)
index 0000000..74204c2
--- /dev/null
@@ -0,0 +1,114 @@
+<div class="panel" id="vmware_config" style="display: none;">
+    <h5 class="box-title">
+        <a data-toggle="collapse" data-parent="#accordion" href="#vmware_config_collapse">
+            Config parameters
+        </a>
+    </h5>
+    <div id="vmware_config_collapse" class="panel-collapse collapse ">
+        <div class="col-lg-6">
+            <div class="row">
+
+                <label for="vmware_sdn_ctrl" class="col-sm-4 control-label">SDN Controller</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vmware_sdn_ctrl"> {{datacenter.sdn_ctrl|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="vmware_sdn_port_map" class="col-sm-4 control-label">SDN Port Mapping</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vmware_sdn_port_map"> {{datacenter.sdn_port_map|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="orgname" class="col-sm-4 control-label">Org. name</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="orgname"> {{datacenter.orgname|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="admin_username" class="col-sm-4 control-label">Admin username</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="admin_username"> {{datacenter.admin_username|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="admin_password" class="col-sm-4 control-label">Admin password</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="admin_password"> {{datacenter.admin_password|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="nsx_manager" class="col-sm-4 control-label">Nsx manager</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="nsx_manager"> {{datacenter.nsx_manager|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="nsx_user" class="col-sm-4 control-label">Nsx user</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="nsx_user"> {{datacenter.nsx_user|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="nsx_password" class="col-sm-4 control-label">Nsx password</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="nsx_password"> {{datacenter.nsx_password|default:"---" }}</p>
+                </div>
+            </div>
+        </div>
+        <div class="col-lg-6">
+            <div class="row">
+
+                <label for="vcenter_ip" class="col-sm-4 control-label">Vcenter ip</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vcenter_ip"> {{datacenter.vcenter_ip|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="vcenter_port" class="col-sm-4 control-label">Vcenter port</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vcenter_port"> {{datacenter.vcenter_port|default:"---" }}</p></div>
+            </div>
+            <div class="row">
+
+                <label for="vcenter_user" class="col-sm-4 control-label">Vcenter user</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vcenter_user"> {{datacenter.vcenter_user|default:"---" }}</p></div>
+            </div>
+            <div class="row">
+
+                <label for="vcenter_password" class="col-sm-4 control-label">Vcenter password</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vcenter_password"> {{datacenter.vcenter_password|default:"---" }}</p>
+                </div>
+            </div>
+            <div class="row">
+
+                <label for="vrops_site" class="col-sm-4 control-label">Vrops site</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vrops_site"> {{datacenter.vrops_site|default:"---" }}</p></div>
+            </div>
+            <div class="row">
+
+                <label for="vrops_user" class="col-sm-4 control-label">Vrops user</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vrops_user"> {{datacenter.vrops_user|default:"---" }}</p></div>
+            </div>
+            <div class="row">
+
+                <label for="vrops_password" class="col-sm-4 control-label">Vrops password</label>
+                <div class="col-sm-6 input-group input-group-sm">
+                    <p id="vrops_password"> {{datacenter.vrops_password|default:"---" }}</p>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/modal/vim_details.html b/vimhandler/template/modal/vim_details.html
new file mode 100644 (file)
index 0000000..cb0bfa6
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="modal" id="modal_show_vim" xmlns="http://www.w3.org/1999/html">
+    <div class="modal-dialog">
+        <div class="modal-content">
+            <div class="modal-header">
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span></button>
+                <h4 class="modal-title" >Vim Details</h4>
+            </div>
+
+            <div class="modal-body" id="modal_show_vim_body">
+
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+            </div>
+        </div>
+        <!-- /.modal-content -->
+    </div>
+    <!-- /.modal-dialog -->
+</div>
\ No newline at end of file
diff --git a/vimhandler/template/vim_create.html b/vimhandler/template/vim_create.html
new file mode 100644 (file)
index 0000000..477f0be
--- /dev/null
@@ -0,0 +1,189 @@
+{% extends "base.html" %}
+{% load get %}
+{% load staticfiles %}
+{% block head_base %}
+    {% with skin_css="AdminLTE/dist/css/skins/skin-purple.min.css"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block body %}
+    {% with skin="purple"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+{% block logo_sidebar %}
+    {% with logo_mini="assets/img/osm_small_logo.png" logo="assets/img/OSM-logo.png" %}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+
+
+{% block head_block %}
+    {{ block.super }}
+
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+
+    {% include 'left_sidebar_base.html' %}
+
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+    {{ block.super }}
+    <li><a href="{% url 'vim:list' %}">Vims</a></li>
+{% endblock %}
+
+{% block content_body %}
+    {{ block.super }}
+
+<div class="row">
+    <div class="col-md-12">
+        <form action='{% url "vim:create" %}' method="post">
+            {% csrf_token %}
+            <div class="box">
+                <div class="box-header with-border">
+                    <h3 class="box-title">New VIM Account</h3>
+                    <div class="box-tools">
+
+                    </div>
+                </div>
+                <div class="box-body">
+
+                    <div class="row">
+                        <div class="col-lg-6">
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="name" class="col-sm-3 control-label">Name*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="name" name="name" placeholder="VIM name" required>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="vim_user" class="col-sm-3 control-label">Vim Username*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="vim_user" name="vim_user" placeholder="Vim Username" required>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="vim_password" class="col-sm-3 control-label">Vim Password*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input type="password" class="form-control" id="vim_password" name="vim_password" placeholder="Vim Password" required>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="vim_url" class="col-sm-3 control-label">Vim URL*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input type="url" class="form-control" id="vim_url" name="vim_url" placeholder="Vim URL" required>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="vim_type" class="col-sm-3 control-label">Type*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <select id="vim_type" name="vim_type" class="form-control" onchange="toggleConfig(this)">
+                                            <option value="openvim">OpenVIM</option>
+                                          <option value="openstack">Openstack</option>
+                                          <option value="vmware">VMware vCD</option>
+                                          <option value="aws">AWS</option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+
+
+
+
+
+
+                        </div>
+                        <div class="col-lg-6">
+                                                        <div class="row">
+                                <div class="form-group">
+                                    <label for="vim_tenant_name" class="col-sm-3 control-label">Tenant name*</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="vim_tenant_name" name="vim_tenant_name" placeholder="Tenant name" required>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="description" class="col-sm-3 control-label">Description</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="description" name="description" placeholder="Description">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="schema_type" class="col-sm-3 control-label">Schema Type</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="schema_type" name="schema_type" placeholder="Schema Type">
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="form-group">
+                                    <label for="schema_version" class="col-sm-3 control-label">Schema Version</label>
+                                    <div class="col-sm-6 input-group input-group-sm">
+                                        <input class="form-control" id="schema_version" name="schema_version" placeholder="Schema Version" >
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+
+
+                    </div>
+                    {% include 'config/openvim.html' %}
+                    {% include 'config/openstack.html' %}
+                    {% include 'config/vmware.html' %}
+                    {% include 'config/aws.html' %}
+
+                </div>
+                <div class="box-footer">
+                    <button onclick="location.href='{% url 'vim:list' %}'" class="btn btn-default pull-left">Back to VIM Accounts</button>
+                    <button class="btn btn-primary pull-right">Create</button>
+                </div>
+            </div>
+        </form>
+    </div>
+
+</div>
+{% endblock %}
+
+{% block resource_block %}
+    {{ block.super }}
+    <!-- Utility JS -->
+
+    <script>
+        $(document).ready(function () {
+            toggleConfig(document.getElementById("vim_type"));
+        });
+        function toggleConfig(element){
+            $('.panel').hide();
+            $('.config-input').prop('disabled', true);
+            var type_selected = element.options[element.selectedIndex].value;
+            console.log("type selected: " + type_selected);
+            var div_config_index = type_selected + '_config';
+            $('#'+div_config_index).show();
+            $('#'+div_config_index+' .config-input').prop('disabled', false);
+        }
+    </script>
+
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
\ No newline at end of file
diff --git a/vimhandler/template/vim_list.html b/vimhandler/template/vim_list.html
new file mode 100644 (file)
index 0000000..dddde82
--- /dev/null
@@ -0,0 +1,105 @@
+{% extends "base.html" %}
+{% load get %}
+{% load staticfiles %}
+
+
+{% block head_block %}
+    {{ block.super }}
+
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+
+    {% include 'left_sidebar_base.html' %}
+
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+    {{ block.super }}
+    <li><a href="{% url 'vim:list' %}">Vims</a></li>
+{% endblock %}
+
+{% block content_body %}
+    {{ block.super }}
+    {% include 'modal/vim_details.html' %}
+    {% csrf_token %}
+    <div class="row">
+        <div class="col-md-12">
+
+            <div class="box">
+                <div class="box-header with-border">
+                    <h3 class="box-title">Registered VIM</h3>
+                    <div class="box-tools">
+                        <a href='{% url "vim:create" %}' class="btn btn-block btn-primary btn-sm"><i
+                                class="fa fa-plus"></i><span> New VIM</span></a>
+                    </div>
+                </div>
+                <div class="box-body">
+                    <table id="projects_table" class="table table-bordered table-striped">
+                        <thead>
+                        <tr>
+                            <th>Id</th>
+                            <th>Name</th>
+                            <th>Type</th>
+                            <th>Operational State</th>
+                            <th>Description</th>
+                            <th>Actions</th>
+                        </tr>
+                        </thead>
+                        <tbody>
+                        {% for p in datacenters %}
+
+                            <tr>
+
+                                <td>{{ p|get:"_id" }}</td>
+                                <td>{{ p|get:"name" }}</td>
+                                <td>{{ p|get:"vim_type" }}</td>
+                                <td>{{ p|get_sub:"_admin,operationalState"}}</td>
+                                <td>{{ p|get_sub:"_admin,description" }}</td>
+
+
+                                <td>
+                                    <div class="btn-group">
+                                        <button type="button" class="btn btn-default"
+                                                onclick="location.href='{% url "vim:show" vim_id=p|get:"_id" %}'" data-toggle="tooltip" data-placement="top" data-container="body" title="Show Info"><i
+                                                class="fa fa-info"></i></button>
+                                        <button type="button" class="btn btn-default"
+                                                onclick="javascript:deleteVim('{{ p|get:"_id" }}')" data-toggle="tooltip" data-placement="top" data-container="body" title="Delete"><i
+                                                class="fa fa-trash-o" ></i></button>
+                                    </div>
+                                </td>
+
+                            </tr>
+                        {% endfor %}
+                        </tbody>
+                    </table>
+                </div>
+            </div>
+        </div>
+
+    </div>
+{% endblock %}
+
+{% block resource_block %}
+    {{ block.super }}
+    <!-- Utility JS -->
+
+    <script>
+
+        function deleteVim(vim_uuid) {
+            bootbox.confirm("Are you sure want to delete?", function (result) {
+                if (result) {
+                    location.href = '/vims/' + vim_uuid + '/delete'
+                }
+            })
+        }
+    </script>
+
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
\ No newline at end of file
diff --git a/vimhandler/template/vim_show.html b/vimhandler/template/vim_show.html
new file mode 100644 (file)
index 0000000..3d34941
--- /dev/null
@@ -0,0 +1,148 @@
+{% extends "base.html" %}
+{% load get %}
+{% load staticfiles %}
+{% block head_base %}
+    {% with skin_css="AdminLTE/dist/css/skins/skin-purple.min.css"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+{% block body %}
+    {% with skin="purple"%}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+{% block logo_sidebar %}
+    {% with logo_mini="assets/img/osm_small_logo.png" logo="assets/img/OSM-logo.png" %}
+        {{ block.super }}
+    {% endwith %}
+{% endblock %}
+
+
+
+{% block head_block %}
+    {{ block.super }}
+
+{% endblock %}
+{% block title_header_big %}
+    {{ block.super }}
+{% endblock %}
+{% block left_sidebar %}
+
+    {% include 'left_sidebar_base.html' %}
+
+{% endblock %}
+
+
+{% block breadcrumb_body %}
+    {{ block.super }}
+    <li><a href="{% url 'vim:list' %}">Vims</a></li>
+{% endblock %}
+
+{% block content_body %}
+    {{ block.super }}
+
+<div class="row">
+    <div class="col-md-12">
+
+        <div class="box">
+            <div class="box-header with-border">
+                <h3 class="box-title">VIM Account details</h3>
+                <div class="box-tools">
+
+                </div>
+            </div>
+            <div class="box-body">
+
+                <div class="row">
+                    <div class="col-lg-6">
+                        <div class="row">
+                            <label for="name" class="col-sm-3 control-label">Name</label>
+                            <div class="col-sm-6 input-group input-group-sm">
+                                <p id="name"> {{datacenter.name|default:"---" }}</p>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <label for="vim_user" class="col-sm-3 control-label">Vim Username</label>
+                            <div class="col-sm-6 input-group input-group-sm">
+                                <p id="vim_user"> {{datacenter.vim_user|default:"---" }}</p>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <label for="vim_url" class="col-sm-3 control-label">Vim URL</label>
+                            <div class="col-sm-6 input-group input-group-sm">
+                                <p id="vim_url"> {{datacenter.vim_url|default:"---" }}</p>
+                            </div>
+                        </div>
+                        <div class="row">
+                            <label for="vim_type" class="col-sm-3 control-label">Type</label>
+                            <div class="col-sm-6 input-group input-group-sm">
+                                <p id="vim_type"> {{datacenter.vim_type|default:"---" }}</p>
+                            </div>
+                        </div>
+
+
+                    </div>
+                    <div class="col-lg-6">
+                        <div class="row">
+                                <label for="vim_tenant_name" class="col-sm-3 control-label">Tenant name</label>
+                                <div class="col-sm-6 input-group input-group-sm">
+                                    <p id="vim_tenant_name"> {{datacenter.vim_tenant_name|default:"---" }}</p>
+                                </div>
+                        </div>
+                        <div class="row">
+                                <label for="description" class="col-sm-3 control-label">Description</label>
+                                <div class="col-sm-6 input-group input-group-sm">
+                                    <p id="description"> {{datacenter.description|default:"---" }}</p>
+                                </div>
+                        </div>
+                        <div class="row">
+                                <label for="schema_type" class="col-sm-3 control-label">Schema Type</label>
+                                <div class="col-sm-6 input-group input-group-sm">
+                                    <p id="schema_type"> {{datacenter.schema_type|default:"---" }}</p>
+                                </div>
+                        </div>
+                        <div class="row">
+                                <label for="schema_version" class="col-sm-3 control-label">Schema Version</label>
+                                <div class="col-sm-6 input-group input-group-sm">
+                                    <p id="schema_version"> {{datacenter.schema_version|default:"---" }}</p>
+                                </div>
+                        </div>
+                    </div>
+
+
+                </div>
+                {% if datacenter.vim_type == 'openvim' %}
+                    {% include 'config/openvim_show.html' %}
+                {% elif datacenter.vim_type == 'openstack' %}
+                    {% include 'config/openstack_show.html' %}
+                {% elif datacenter.vim_type == 'vmware' %}
+                    {% include 'config/vmware_show.html' %}
+                {% elif datacenter.vim_type == 'aws' %}
+                    {% include 'config/aws_show.html' %}
+                {% endif %}
+            </div>
+            <div class="box-footer">
+                <div class="">
+                    <button onclick="location.href='{% url 'vim:list' %}'" class="btn btn-primary">Back to VIM Accounts</button>
+                </div>
+            </div>
+        </div>
+    </div>
+
+</div>
+{% endblock %}
+
+{% block resource_block %}
+    {{ block.super }}
+    <!-- Utility JS -->
+
+    <script>
+
+    </script>
+
+{% endblock %}
+
+{% block footer %}
+    {% include "footer.html"  %}
+{% endblock %}
\ No newline at end of file
diff --git a/vimhandler/tests.py b/vimhandler/tests.py
new file mode 100644 (file)
index 0000000..79947e6
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.test import TestCase
+
+# Create your tests here.
diff --git a/vimhandler/urls.py b/vimhandler/urls.py
new file mode 100644 (file)
index 0000000..d993e0a
--- /dev/null
@@ -0,0 +1,26 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.conf.urls import url
+from vimhandler import views
+
+urlpatterns = [
+    url(r'^list/', views.list, name='list'),
+    url(r'^create/', views.create, name='create'),
+    url(r'^(?P<vim_id>[0-9a-z-]+)/delete$', views.delete, name='delete'),
+    url(r'^(?P<vim_id>[0-9a-z-]+)', views.show, name='show'),
+
+]
\ No newline at end of file
diff --git a/vimhandler/views.py b/vimhandler/views.py
new file mode 100644 (file)
index 0000000..817a052
--- /dev/null
@@ -0,0 +1,91 @@
+#
+#   Copyright 2018 CNIT - Consorzio Nazionale Interuniversitario per le Telecomunicazioni
+#
+#   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  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 django.shortcuts import render, redirect
+from django.contrib.auth.decorators import login_required, permission_required
+from django.http import HttpResponse, JsonResponse
+from lib.osm.osmclient.client import Client
+import json
+import logging
+
+logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger('helper.py')
+
+@login_required
+def list(request):
+    client = Client()
+    result = client.vim_list()
+    print result
+    result = {
+        "datacenters": result
+    }
+    return __response_handler(request, result, 'vim_list.html')
+
+@login_required
+def create(request):
+    result = {}
+    if request.method == 'GET':
+        return __response_handler(request, result, 'vim_create.html')
+    else:
+        new_vim_dict = request.POST.dict()
+        client = Client()
+        keys = ["schema_version",
+                "schema_type",
+                "name",
+                "vim_url",
+                "vim_type",
+                "vim_user",
+                "vim_password",
+                "vim_tenant_name",
+                "description"]
+        vim_data = dict(filter(lambda i: i[0] in keys and len(i[1]) > 0, new_vim_dict.items()))
+        vim_data['config']={}
+        for k,v in new_vim_dict.items():
+            if str(k).startswith('config_') and len(v) > 0:
+                config_key = k[7:]
+                vim_data['config'][config_key] = v
+        print vim_data
+        result = client.vim_create(vim_data)
+        # TODO  'vim:show', to_redirect=True, vim_id=vim_id
+        return __response_handler(request, result, 'vim:list', to_redirect=True)
+
+@login_required
+def delete(request, vim_id=None):
+    try:
+        client = Client()
+        del_res = client.vim_delete(vim_id)
+    except Exception as e:
+        log.exception(e)
+    return __response_handler(request, {}, 'vim:list', to_redirect=True)
+
+@login_required
+def show(request, vim_id=None):
+    client = Client()
+    datacenter = client.vim_get(vim_id)
+    print datacenter
+    return __response_handler(request, {
+        "datacenter": datacenter
+    }, 'vim_show.html')
+
+
+def __response_handler(request, data_res, url=None, to_redirect=None, *args, **kwargs):
+    raw_content_types = request.META.get('HTTP_ACCEPT', '*/*').split(',')
+    if 'application/json' in raw_content_types:
+        return JsonResponse(data_res)
+    elif to_redirect:
+        return redirect(url, *args, **kwargs)
+    else:
+        return render(request, url, data_res)