From: lombardof Date: Thu, 3 May 2018 14:20:04 +0000 (+0200) Subject: first commit X-Git-Tag: v4.0.0~9 X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FLW-UI.git;a=commitdiff_plain;h=fb37bca4b7e8178c65df2f1496afe372c1d55e78 first commit Change-Id: I8a65ee5527dd16d81e87c8ac5d4bdb471e5e759d Signed-off-by: lombardof --- diff --git a/.bowerrc b/.bowerrc new file mode 100644 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 index 0000000..06fb943 --- /dev/null +++ b/.gitignore @@ -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 index 0000000..56b0497 --- /dev/null +++ b/Dockerfile @@ -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 index 0000000..c865b36 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ + + +# 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 index 0000000..136f061 --- /dev/null +++ b/bower.json @@ -0,0 +1,27 @@ +{ + "name": "osm-lw-ui", + "authors": [ + "Francesco Lombardo " + ], + "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 index 0000000..e69de29 diff --git a/instancehandler/admin.py b/instancehandler/admin.py new file mode 100644 index 0000000..2e9690a --- /dev/null +++ b/instancehandler/admin.py @@ -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 index 0000000..2f49d31 --- /dev/null +++ b/instancehandler/apps.py @@ -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 index 0000000..21d5735 --- /dev/null +++ b/instancehandler/models.py @@ -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 index 0000000..1b0868d --- /dev/null +++ b/instancehandler/template/instance_list.html @@ -0,0 +1,152 @@ +{% extends "base.html" %} +{% load get %} +{% load staticfiles %} + + + +{% block head_block %} + {{ block.super }} + + + + + + +{% endblock %} +{% block title_header_big %} + {{ block.super }} +{% endblock %} +{% block left_sidebar %} + {% include 'osm/osm_project_left_sidebar.html' %} +{% endblock %} + + +{% block breadcrumb_body %} + {{ block.super }} +
  • Instances
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + {% include 'modal/instance_create.html' %} + {% csrf_token %} +
    +
    + +
    +
    +

    Instances

    + +
    +
    + + + + + + + + + + + + + + {% for i in instances %} + + + + + + + {% if i|get:"operational-status" == 'failed' %} + + {% elif i|get:"operational-status" == 'init' %} + + {% elif i|get:"operational-status" == 'running' %} + + {% else %} + + {% endif %} + {% if i|get:"config-status" == 'failed' %} + + {% elif i|get:"config-status" == 'init' %} + + {% elif i|get:"config-status" == 'running' %} + + {% elif i|get:"config-status" == 'configured' %} + + {% else %} + + {% endif %} + + + + + + {% endfor %} + +
    IdNameNsd nameOperational StatusConfig StatusDetailed StatusActions
    {{ i|get:"_id" }}{{ i|get:"short-name" }}{{ i|get:"nsd-name-ref" }}{{ i|get:"operational-status" }} {{ i|get:"operational-status" }} {{ i|get:"operational-status" }} {{ i|get:"operational-status" }}{{ i|get:"config-status" }} {{ i|get:"config-status" }} {{ i|get:"config-status" }} {{ i|get:"config-status" }} {{ i|get:"config-status" }}{{ i|get:"detailed-status" }} +
    + + + + + + + + +
    + +
    +
    +
    +
    + +
    +{% include 'modal/instance_show.html' %} +{% include 'modal/instance_new_action.html' %} +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + + + + + + + + + + + + + + +{% 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 index 0000000..c40ecb2 --- /dev/null +++ b/instancehandler/template/modal/instance_create.html @@ -0,0 +1,55 @@ + diff --git a/instancehandler/template/modal/instance_new_action.html b/instancehandler/template/modal/instance_new_action.html new file mode 100644 index 0000000..6cf3059 --- /dev/null +++ b/instancehandler/template/modal/instance_new_action.html @@ -0,0 +1,57 @@ + diff --git a/instancehandler/template/modal/instance_show.html b/instancehandler/template/modal/instance_show.html new file mode 100644 index 0000000..6df8bc1 --- /dev/null +++ b/instancehandler/template/modal/instance_show.html @@ -0,0 +1,23 @@ + diff --git a/instancehandler/tests.py b/instancehandler/tests.py new file mode 100644 index 0000000..79947e6 --- /dev/null +++ b/instancehandler/tests.py @@ -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 index 0000000..b6a9e33 --- /dev/null +++ b/instancehandler/urls.py @@ -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[ns|vnf]+)/list/', views.list, name='list'), + url(r'^create/', views.create, name='create'), + url(r'^(?P[ns|vnf]+)/(?P[0-9a-z-]+)/delete$', views.delete, name='delete'), + url(r'^(?P[ns|vnf]+)/(?P[0-9a-z-]+)/action', views.action, name='action'), + url(r'^(?P[ns|vnf]+)/(?P[0-9a-z-]+)', views.show, name='show'), + +] diff --git a/instancehandler/views.py b/instancehandler/views.py new file mode 100644 index 0000000..0c9a8a4 --- /dev/null +++ b/instancehandler/views.py @@ -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 index 0000000..04633f5 --- /dev/null +++ b/lib/TopologyModels/example/example.yaml @@ -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 index 0000000..59ac883 --- /dev/null +++ b/lib/TopologyModels/osm/osm.yaml @@ -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 index 0000000..bfcc6bf --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1 @@ +__all__ = ["Util", 'etsi'] diff --git a/lib/osm/__init__.py b/lib/osm/__init__.py new file mode 100644 index 0000000..26d8656 --- /dev/null +++ b/lib/osm/__init__.py @@ -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 index 0000000..a596405 --- /dev/null +++ b/lib/osm/osm_parser.py @@ -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 index 0000000..91b798f --- /dev/null +++ b/lib/osm/osm_rdcl_graph.py @@ -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 index 0000000..e69de29 diff --git a/lib/osm/osmclient/client.py b/lib/osm/osmclient/client.py new file mode 100644 index 0000000..48c9815 --- /dev/null +++ b/lib/osm/osmclient/client.py @@ -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 index 0000000..1b764d2 --- /dev/null +++ b/lib/parser.py @@ -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 index 0000000..9d96251 --- /dev/null +++ b/lib/rdcl_graph.py @@ -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 index 0000000..71fc36b --- /dev/null +++ b/lib/util.py @@ -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 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 index 0000000..e69de29 diff --git a/projecthandler/admin.py b/projecthandler/admin.py new file mode 100644 index 0000000..d85d57a --- /dev/null +++ b/projecthandler/admin.py @@ -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 index 0000000..b71d1e7 --- /dev/null +++ b/projecthandler/apps.py @@ -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 index 0000000..e69de29 diff --git a/projecthandler/management/commands/__init__.py b/projecthandler/management/commands/__init__.py new file mode 100644 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 index 0000000..d750f9f --- /dev/null +++ b/projecthandler/management/commands/delete_project_type.py @@ -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 index 0000000..7c9dce9 --- /dev/null +++ b/projecthandler/management/commands/new_project_type.py @@ -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 index 0000000..66fb7a5 --- /dev/null +++ b/projecthandler/models.py @@ -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 index 0000000..4776010 --- /dev/null +++ b/projecthandler/osm_model.py @@ -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 index 0000000..3635d66 --- /dev/null +++ b/projecthandler/template/project/descriptor/descriptor_view_base.html @@ -0,0 +1,131 @@ +{% extends "base.html" %} + +{% load staticfiles %} + +{% block head_block %} + {{ block.super }} + + + + + + + + + +{% endblock %} + + + + +{% block content_body %} + {{ block.super }} +
    +
    + + +
    +
    +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + + + + + + + + + + + + + + + + + +{% 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 index 0000000..3bfea9e --- /dev/null +++ b/projecthandler/template/project/descriptor/modal/choose_node_id.html @@ -0,0 +1,27 @@ + \ 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 index 0000000..1986d5d --- /dev/null +++ b/projecthandler/template/project/modal/modal_keyboard_info_base.html @@ -0,0 +1,37 @@ + + \ 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 index 0000000..44fb435 --- /dev/null +++ b/projecthandler/template/project/new_project.html @@ -0,0 +1,129 @@ +{% 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 }} +
  • New Project
  • +{% endblock %} + +{% block content_body %} +{{ block.super }} +
    +
    + +
    +
    +

    New Project

    +
    + + +
    + {% csrf_token %} +
    +
    +
    +

    Project Type

    + + + +
    +
    +

    Project Name

    + +
    +
    +

    Info

    + +
    + + +
    +
    + +
    +
    +
    + {% autoescape off %}{{ type_container_template }}{% endautoescape %} +
    +
    + + +
    + +
    +
    + + +
    +
    +{% endblock %} + +{% block resource_block %} +{{ block.super }} + + + + + +{% 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 index 0000000..d257d7f --- /dev/null +++ b/projecthandler/template/project/osm/descriptor/descriptor_new.html @@ -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 }} +
  • {{project_overview_data.name}}
  • +
  • {{ descriptor_type }} Descriptors
  • +
  • Create Descriptor
  • +{% endblock %} + +{% block tab_pane_button_list %} +{{ block.super }} +
  • Onboard Package
  • +{% endblock %} + +{% block nav_buttons_list %} +{{ block.super }} +
  • + +
  • +{% endblock %} + +{% block tab_pane_list %} +{{ block.super }} + +
    + +

    Drag and drop file below

    +
    + Just drag and drop files here +
    +
    +{% endblock %} + + +{% block resource_block %} +{{ block.super }} + + +{% 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 index 0000000..5a082de --- /dev/null +++ b/projecthandler/template/project/osm/descriptor/descriptor_view.html @@ -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 }} +
  • {{project_overview_data.name}}
  • +
  • {{ descriptor_type }} Descriptors
  • +
  • {{descriptor_id}}
  • +{% endblock %} + +{% block tab_pane_button_list %} +{{ block.super }} +
  • YAML
  • +
  • JSON
  • +{% endblock %} + +{% block nav_buttons_list %} + {{ block.super }} +
  • +
  • +
  • +{% endblock %} + +{% block tab_pane_list %} +{{ block.super }} + + +
    + +
    + + +
    + +
    + +{% endblock %} + +{% block resource_block %} +{{ block.super }} + + + +{% 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 index 0000000..56ee44d --- /dev/null +++ b/projecthandler/template/project/osm/descriptor/descriptorlist.html @@ -0,0 +1,57 @@ +{% load staticfiles %} +{% load get %} +
    + +
    + +
    + + + + + + + + + + + + + {% for k in descriptors %} + + + + + + + + + {% endfor %} + + +
    IdNameDescriptionVendorVersionActions
    {{ k|get:"_id" }}{{ k|get:"short-name" }} {{ k|get:"description" }} {{ k|get:"vendor" }} {{ k|get:"version" }} +
    + {% if descriptor_type == "nsd" %} + + {% endif %} + + + + + +
    + +
    +
    +
    +
    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 index 0000000..91c4d20 --- /dev/null +++ b/projecthandler/template/project/osm/descriptor/modal/choose_node_id.html @@ -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 index 0000000..ed9a4a5 --- /dev/null +++ b/projecthandler/template/project/osm/descriptor/modal/create_link_chooser.html @@ -0,0 +1,21 @@ + \ 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 index 0000000..13105fe --- /dev/null +++ b/projecthandler/template/project/osm/modal/files_list.html @@ -0,0 +1,33 @@ + 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 index 0000000..bf39e69 --- /dev/null +++ b/projecthandler/template/project/osm/modal/modal_keyboard_info.html @@ -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 index 0000000..cfdec29 --- /dev/null +++ b/projecthandler/template/project/osm/osm_new_project.html @@ -0,0 +1,25 @@ + + + \ 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 index 0000000..50bf685 --- /dev/null +++ b/projecthandler/template/project/osm/osm_project_delete.html @@ -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 index 0000000..30ea76a --- /dev/null +++ b/projecthandler/template/project/osm/osm_project_descriptors.html @@ -0,0 +1,121 @@ +{% extends "project_descriptors.html" %} +{% load staticfiles %} + + +{% block head_block %} + {{ block.super }} + +{% 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 }} +
  • {{project_overview_data.name}}
  • +
  • {% if descriptor_type %} + {{ descriptor_type }} + {% else%} + Undefined + {% endif %} Packages
  • + +{% endblock %} + +{% block content_body %} + {{ block.super }} +{% include 'osm/modal/files_list.html' %} +{% include 'modal/instance_create.html' %} +
    + {% include "osm/descriptor/descriptorlist.html" %} +
    +{% endblock %} + + + + +{% block resource_block %} + {{ block.super }} + + +{% 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 index 0000000..ab90385 --- /dev/null +++ b/projecthandler/template/project/osm/osm_project_details.html @@ -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 %} + +
    + +
    +
    +
    +
    +

    {{ project_overview.nsd }}

    +

    NS packages

    +
    + Open list
    +
    +
    +
    +

    {{ project_overview.vnfd }}

    +

    VNF packages

    +
    + Open list
    +
    +
    +
    +
    +
    +

    {{ project_overview.ns }}

    +

    NS Instances

    +
    + Open list
    +
    + +
    + +
    +
    + +{% 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 index 0000000..029ae11 --- /dev/null +++ b/projecthandler/template/project/osm/osm_project_left_sidebar.html @@ -0,0 +1,56 @@ + \ 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 index 0000000..15d2eeb --- /dev/null +++ b/projecthandler/template/project/osm/project_graph.html @@ -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 }} + + + + + + + + +{% 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 index 0000000..9a1f528 --- /dev/null +++ b/projecthandler/template/project/osm/topology_toolbar.html @@ -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 index 0000000..e6afc5e --- /dev/null +++ b/projecthandler/template/project/project_delete.html @@ -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 %} +
  • {{project_overview_data.name}}
  • +
  • Delete Project
  • + {% endif %} +{% endblock %} + +{% block content_body %} + {{ block.super }} +
    + {% if project_id %} +
    +
    +
    + + +

    Warning

    +
    +
    +
    + {% csrf_token %} +
    +

    The Project {{project_name}} will be deleted!

    + +
    + +
    + +
    +
    +
    + {% endif %} +
    + +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + +{% endblock %} + diff --git a/projecthandler/template/project/project_descriptors.html b/projecthandler/template/project/project_descriptors.html new file mode 100644 index 0000000..6f7345e --- /dev/null +++ b/projecthandler/template/project/project_descriptors.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} + +{% load staticfiles %} + +{% block head_block %} + {{ block.super }} + +{% endblock %} +{% block title_header_big %} + +{% endblock %} + +{% block left_sidebar %} + +{% endblock %} + +{% block breadcrumb_body %} + {{ block.super }} +
  • Projects
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} +{% if alert_message %} + {% if alert_message.success %} +
    + +

    Success

    + {{alert_message.message}} +
    + {% endif %} + {% if not alert_message.success %} +
    + +

    Alert!

    + {{alert_message.message}} +
    + {% endif %} +{% endif %} + +{% endblock %} + + + + +{% block resource_block %} + {{ block.super }} + + + + + +{% endblock %} + diff --git a/projecthandler/template/project/project_details.html b/projecthandler/template/project/project_details.html new file mode 100644 index 0000000..e944e0d --- /dev/null +++ b/projecthandler/template/project/project_details.html @@ -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 }} +
  • Projects
  • +
  • {{project_overview.name}}
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + + +{% 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 index 0000000..7d344da --- /dev/null +++ b/projecthandler/template/project/project_graph_base.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% load staticfiles %} + +{% block head_block %} + {{ block.super }} + + +{% endblock %} +{% block title_header_big %} + {{ block.super }} +{% endblock %} +{% block left_sidebar %} + +{% endblock %} + + +{% block breadcrumb_body %} + {{ block.super }} +
  • {{project_overview_data.name}}
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + {% csrf_token %} +
    +
    + {% block topology_toolbar %} + + + {% endblock %} +
    + +
    +
    +
    + + +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + + + + + + + +{% endblock %} + + +{% block header %} +{% endblock %} + diff --git a/projecthandler/template/project/projectlist.html b/projecthandler/template/project/projectlist.html new file mode 100644 index 0000000..5fd3b9d --- /dev/null +++ b/projecthandler/template/project/projectlist.html @@ -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 }} +
  • Projects
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + {% csrf_token %} +
    +
    + +
    +
    +

    Projects

    + +
    +
    + + + + + + + + + + + + + + + + + {% for p in projects %} + + + + + + + + + + + + + {% endfor %} + +
    NameInfoTypeModification DateCreation DateOwner
    + {{ p.name }} + {{ p.info }}{{ p.get_type }}{{ p.updated_date }}{{ p.created_date }}{{ p.owner.username }} Delete
    +
    +
    +
    + +
    +{% 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 index 0000000..3367eeb --- /dev/null +++ b/projecthandler/template/project/repository/repo_base_left_sidebar.html @@ -0,0 +1,12 @@ +{% extends "left_sidebar_base.html" %} + +{% block left_sidebar_base %} +
  • REPOSITORY
  • + {% url "repos:repos_list" as repo_list_url %} +
  • Ropository Registered
  • + + {% 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 index 0000000..6df53b5 --- /dev/null +++ b/projecthandler/template/project/repository/repo_create_modal.html @@ -0,0 +1,43 @@ + \ 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 index 0000000..1f0a8f3 --- /dev/null +++ b/projecthandler/template/project/repository/repo_list.html @@ -0,0 +1,120 @@ +{% extends "base.html" %} + +{% load staticfiles %} + +{% block head_block %} +{{ block.super }} + + +{% endblock %} + +{% block title_header_big %} +{{ block.super }} +{% endblock %} + +{% block left_sidebar %} + +{% include "repository/repo_base_left_sidebar.html" %} +{% endblock %} + + +{% block breadcrumb_body %} +{{ block.super }} + +{% endblock %} + +{% block content_body %} +{{ block.super }} +{% csrf_token %} +
    + +
    + +
    +
    +

    Registered Repository

    + +
    +
    + + + + + + + + + + + + {% for repo in repos %} + + + + + + + + + {% endfor %} + +
    NameUrlLast UpdateActions
    + {{ repo.name }} + + {{ repo.base_url }} + {{ repo.last_update }} +
    + + + +
    +
    +
    +
    +
    + +
    +{% 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 index 0000000..d334413 --- /dev/null +++ b/projecthandler/tests.py @@ -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 index 0000000..e69de29 diff --git a/projecthandler/urls/project.py b/projecthandler/urls/project.py new file mode 100644 index 0000000..61e4d4e --- /dev/null +++ b/projecthandler/urls/project.py @@ -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\d+)/$', views.open_project, name='open_project'), + url(r'^(?P\d+)/instances/', include('instancehandler.urls', namespace='instances'), name='instances_base'), + url(r'^(?P\d+)/delete$', views.delete_project, name='delete_project'), + url(r'^(?P\d+)/graph(/$)', views.graph, name='graph_view'), + url(r'^(?P\d+)/graph/graph_data(/$)', views.graph_data, name='graph_data'), + url(r'^(?P\d+)/graph/graph_data/(?P[-\w]+)(/$)', views.graph_data, name='graph_data'), + url(r'^(?P\d+)/graph/positions$', views.graph_positions, name='graph_positions'), + url(r'^(?P\d+)/graph/unusedvnf/(?P\w+)(/$)', views.unused_vnf, name='unused_vnf'), + url(r'^(?P\d+)/graph/addelement$', views.add_element, name='addelement'), + url(r'^(?P\d+)/graph/overviewelement$', views.overviewelement, name='overviewelement'), + url(r'^(?P\d+)/graph/addnodetovnffg', views.add_node_to_vnffg, name='addnodetovnffg'), + url(r'^(?P\d+)/graph/removeelement$', views.remove_element, name='removeelement'), + url(r'^(?P\d+)/graph/addlink$', views.add_link, name='addlink'), + url(r'^(?P\d+)/graph/removelink$', views.remove_link, name='removelink'), + url(r'^(?P\d+)/graph/availablenodes', views.get_available_nodes, name='get_available_nodes'), + url(r'^(?P\d+)/descriptors/(?P\w+)(/$)', views.show_descriptors, name='list_descriptors'), + url(r'^(?P\d+)/descriptors/(?P\w+)/(?P[-\w]+)(/$)', + views.edit_descriptor, name='edit_descriptor'), + url(r'^(?P\d+)/descriptors/(?P\w+)/(?P[-\w]+)/delete$', + views.delete_descriptor, + name='delete_descriptor'), + url(r'^(?P\d+)/descriptors/(?P\w+)/(?P[-\w]+)/clone$', + views.clone_descriptor, + name='clone_descriptor'), + url(r'^(?P\d+)/descriptors/(?P\w+)/(?P[-\w]+)/action/(?P[-\w]+)', + views.custom_action, + name='custom_action'), + url(r'^(?P\d+)/descriptors/(?P\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 index 0000000..8c553a4 --- /dev/null +++ b/projecthandler/views.py @@ -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 index 0000000..99d35b5 --- /dev/null +++ b/requirements.txt @@ -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 index 0000000..e69de29 diff --git a/sf_t3d/context_processor.py b/sf_t3d/context_processor.py new file mode 100644 index 0000000..d554515 --- /dev/null +++ b/sf_t3d/context_processor.py @@ -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 index 0000000..1020acd --- /dev/null +++ b/sf_t3d/settings.py @@ -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 index 0000000..e69de29 diff --git a/sf_t3d/templatetags/get.py b/sf_t3d/templatetags/get.py new file mode 100644 index 0000000..80d3695 --- /dev/null +++ b/sf_t3d/templatetags/get.py @@ -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 index 0000000..0784ed1 --- /dev/null +++ b/sf_t3d/urls.py @@ -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 index 0000000..91590f0 --- /dev/null +++ b/sf_t3d/views.py @@ -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 index 0000000..c52ee6f --- /dev/null +++ b/sf_t3d/wsgi.py @@ -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 index 0000000..e69de29 diff --git a/sf_user/admin.py b/sf_user/admin.py new file mode 100644 index 0000000..2e9690a --- /dev/null +++ b/sf_user/admin.py @@ -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 index 0000000..902aa7d --- /dev/null +++ b/sf_user/apps.py @@ -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 index 0000000..e69de29 diff --git a/sf_user/management/commands/__init__.py b/sf_user/management/commands/__init__.py new file mode 100644 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 index 0000000..0ff087b --- /dev/null +++ b/sf_user/management/commands/clean_guest_data.py @@ -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 index 0000000..8717521 --- /dev/null +++ b/sf_user/management/commands/clearsessions.py @@ -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 index 0000000..6485e82 --- /dev/null +++ b/sf_user/models.py @@ -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 index 0000000..df4ed71 --- /dev/null +++ b/sf_user/sessions.py @@ -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 index 0000000..79947e6 --- /dev/null +++ b/sf_user/tests.py @@ -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 index 0000000..fb33347 --- /dev/null +++ b/sf_user/views.py @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 index 0000000..5f5033e --- /dev/null +++ b/static/assets/img/osm_logo.svg @@ -0,0 +1 @@ +osm-logo_color_rgb \ 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 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 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 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 index 0000000..9cb69f5 --- /dev/null +++ b/static/assets/img/rdcl3dlogo0.svg @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RDCL3D + diff --git a/static/assets/img/router.png b/static/assets/img/router.png new file mode 100755 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 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 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 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 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 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 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 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 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 index 0000000..1124aaf --- /dev/null +++ b/static/css/rdcl.css @@ -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 index 0000000..d18cbbe --- /dev/null +++ b/static/src/adminlte_session_storage.js @@ -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 index 0000000..161cc51 --- /dev/null +++ b/static/src/instancehandler/instance_list.js @@ -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: '
    Loading...
    ', + 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 index 0000000..4a5975a --- /dev/null +++ b/static/src/projecthandler/descriptorslist.js @@ -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 index 0000000..9ca4c9e --- /dev/null +++ b/static/src/projecthandler/new_project.js @@ -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 index 0000000..e39e332 --- /dev/null +++ b/static/src/projecthandler/osm/controller.js @@ -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 index 0000000..1af3ef4 --- /dev/null +++ b/static/src/projecthandler/osm/gui_properties.js @@ -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 index 0000000..072129f --- /dev/null +++ b/static/src/projecthandler/osm/project_graph.js @@ -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(''); + $('#modal_chooser_title_add_node').text('Add ' + type_name); + for (var i in vnfs) { + $('#selection_chooser_vnf').append(''); + } + $('#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 = $("
  • " + graph_editor.getCurrentGroup() + "
  • "); + $('#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(''); + } + $('#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('') + for (var i in vnffgIds) { + var vnffgId = vnffgIds[i] + var child = $(''); + $("#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 index 0000000..6d5e820 --- /dev/null +++ b/static/src/utils.js @@ -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('
    ' + + '' + + ' ' + category.category_name + '' + + '
    ' + + '
    ' + + + '
    ' + + '
    '); + 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 = '
    '; + } + 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 = '
    '; + } + + var html_to_append = '
    ' + + '
    ' + type.name + '
    ' + + '
    ' + + palette_node_icon + + '
    ' + + '
    ' + $("#" + 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 '
    '; + break; + case d3.symbolSquare: + return '
    '; + break; + case d3.symbolDiamond: + return '
    '; + ; + break; + case d3.symbolTriangle: + return '
    '; + break; + case d3.symbolStar: + return '
    '; + ; + break; + case d3.symbolCross: + return '
    '; + ; + 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 index 0000000..334513c --- /dev/null +++ b/static/topology3D/css/d3-context-menu.css @@ -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 index 0000000..0433236 --- /dev/null +++ b/static/topology3D/css/graph_editor_d3js.css @@ -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 index 0000000..6f77042 --- /dev/null +++ b/static/topology3D/js/d3-context-menu.js @@ -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 '
    '; + } + 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 index 0000000..cfe815c --- /dev/null +++ b/static/topology3D/js/event.js @@ -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 index 0000000..36e7c8e --- /dev/null +++ b/static/topology3D/js/graph_editor.js @@ -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 index 0000000..055fc1c --- /dev/null +++ b/static/topology3D/js/graph_request.js @@ -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 index 0000000..3c275dd --- /dev/null +++ b/static/topology3D/js/model_graph_editor.js @@ -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 index 0000000..60576d1 --- /dev/null +++ b/template/base.html @@ -0,0 +1,159 @@ +{% load staticfiles %} + + + + + {% block head_base %} + {% include "head.html" %} + {% endblock %} + {% block head_block %} + + {% endblock %} + + + + + + + + +{% block body %} + +{% if request.COOKIES.collapsed_sidebar == '1' %} + +{% else %} + +{% endif %} + +
    + +
    + + {% block logo_sidebar %} + {% include "logo_sidebar.html" %} + {% endblock %} + + +
    + + + {% block left_sidebar %} + + {% endblock %} + + + + +
    + +
    +

    + {% block title_header_big %} + + {% endblock %} + + {% block title_header_small %} + + {% endblock %} + +

    + +
    + + +
    + {% block content_body %} + + {% endblock %} +
    + + {% include "modals/error_alert.html" %} +
    + {% block footer %} + {% include "footer.html" %} + {% endblock %} + + +
    + + + + + + + + + + + + + + + + + + + + + + +{% block resource_block %} + +{% endblock %} + +{% endblock %} + diff --git a/template/error.html b/template/error.html new file mode 100644 index 0000000..a55d97b --- /dev/null +++ b/template/error.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block title_header_big %} + {{ block.super }} + Error Page +{% endblock %} + +{% block content_body %} +
    +

    Error

    + +
    +

    {{error_message}}

    + +

    + Meanwhile, you may return to dashboard . +

    + +
    + +
    +{% endblock %} + +{% block resource_block %} + +{% endblock %} + + diff --git a/template/footer.html b/template/footer.html new file mode 100644 index 0000000..e29c9ad --- /dev/null +++ b/template/footer.html @@ -0,0 +1,12 @@ +{% load staticfiles %} +{% load i18n %} + +
    + + + OSM + + +
    diff --git a/template/forbidden.html b/template/forbidden.html new file mode 100644 index 0000000..4a798c2 --- /dev/null +++ b/template/forbidden.html @@ -0,0 +1,28 @@ +{% extends 'base.html' %} + +{% block title_header_big %} + {{ block.super }} + Error Page +{% endblock %} + +{% block content_body %} +
    +

    Error 403

    + +
    +

    {{error_message}}

    + +

    + Meanwhile, you may return to dashboard . +

    + +
    + +
    +{% endblock %} + +{% block resource_block %} + +{% endblock %} + + diff --git a/template/head.html b/template/head.html new file mode 100644 index 0000000..44ae00c --- /dev/null +++ b/template/head.html @@ -0,0 +1,26 @@ +{% load staticfiles %} +{% load i18n %} + + {{ SITE_NAME }} + + + + + + + + + + + + + + + + + + + + + diff --git a/template/home.html b/template/home.html new file mode 100644 index 0000000..ac1b2f4 --- /dev/null +++ b/template/home.html @@ -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 }} + +
    +
    + +
    +
    +

    {{ projects }}

    +

    Projects

    +

    +
    +
    + +
    + + Open list + +
    +
    + +
    +
    + Photo +
    +{% 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 index 0000000..2ed2e2a --- /dev/null +++ b/template/left_sidebar_base.html @@ -0,0 +1,23 @@ + + diff --git a/template/login.html b/template/login.html new file mode 100644 index 0000000..2323200 --- /dev/null +++ b/template/login.html @@ -0,0 +1,51 @@ +{% extends 'base.html' %} + +{% block content_body %} + + +{% endblock %} + +{% block resource_block %} + +{% endblock %} diff --git a/template/logo_sidebar.html b/template/logo_sidebar.html new file mode 100644 index 0000000..916bef9 --- /dev/null +++ b/template/logo_sidebar.html @@ -0,0 +1,8 @@ +{% load staticfiles %} +{% load i18n %} + diff --git a/template/modals/error_alert.html b/template/modals/error_alert.html new file mode 100644 index 0000000..89a8ed4 --- /dev/null +++ b/template/modals/error_alert.html @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/template/topology_toolbar.html b/template/topology_toolbar.html new file mode 100644 index 0000000..c5f8baa --- /dev/null +++ b/template/topology_toolbar.html @@ -0,0 +1,38 @@ +
    + + + + + + + {% block topology_toolbar_buttons %} + + {% endblock %} + + + + + + + + +
    +{% block boxes_toolbar %} + +{% endblock %} + + + +
    +
    + +
    +
    + +
    +
    diff --git a/vimhandler/__init__.py b/vimhandler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vimhandler/admin.py b/vimhandler/admin.py new file mode 100644 index 0000000..2e9690a --- /dev/null +++ b/vimhandler/admin.py @@ -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 index 0000000..db0dbdf --- /dev/null +++ b/vimhandler/apps.py @@ -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 index 0000000..21d5735 --- /dev/null +++ b/vimhandler/models.py @@ -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 index 0000000..911e927 --- /dev/null +++ b/vimhandler/template/config/aws.html @@ -0,0 +1,79 @@ + + diff --git a/vimhandler/template/config/aws_show.html b/vimhandler/template/config/aws_show.html new file mode 100644 index 0000000..428294a --- /dev/null +++ b/vimhandler/template/config/aws_show.html @@ -0,0 +1,58 @@ +
    +
    + + Config parameters + +
    +
    +
    +
    + +
    +

    {{datacenter.sdn_ctrl||default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.sdn_port_map||default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.region_name||default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.vpc_cidr_block||default:"---" }}

    +
    +
    + +
    + +
    +
    + +
    +

    {{datacenter.security_groups||default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.key_pair||default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.flavor_info||default:"---" }}

    +
    +
    +
    +
    +
    + diff --git a/vimhandler/template/config/openstack.html b/vimhandler/template/config/openstack.html new file mode 100644 index 0000000..63923a0 --- /dev/null +++ b/vimhandler/template/config/openstack.html @@ -0,0 +1,198 @@ + \ 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 index 0000000..7b39222 --- /dev/null +++ b/vimhandler/template/config/openstack_show.html @@ -0,0 +1,130 @@ +
    +
    + + Config parameters + +
    +
    + +
    + +
    + +
    +

    {{datacenter.sdn_ctrl|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.sdn_port_map|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.security_groups|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.availability_zone|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.region_name|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.insecure|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.use_existing_flavors|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.use_internal_endpoint|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.APIversion|default:"---" }}

    +
    +
    + +
    +
    +
    + + +
    +

    {{datacenter.project_domain_id|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.project_domain_name|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.user_domain_id|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.user_domain_name|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.keypair|default:"---" }}

    +
    +
    + + +
    +

    {{datacenter.dataplane_physical_net|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.use_floating_ip|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.dataplane_net_vlan_range|default:"---" }}

    +
    +
    +
    + + +
    +

    {{datacenter.microversion|default:"---" }}

    +
    +
    +
    +
    \ No newline at end of file diff --git a/vimhandler/template/config/openvim.html b/vimhandler/template/config/openvim.html new file mode 100644 index 0000000..1989665 --- /dev/null +++ b/vimhandler/template/config/openvim.html @@ -0,0 +1,27 @@ + \ 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 index 0000000..dbe57dd --- /dev/null +++ b/vimhandler/template/config/openvim_show.html @@ -0,0 +1,23 @@ +
    +
    + + Config parameters + +
    +
    + +
    + + +
    +

    {{datacenter.sdn_ctrl|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.sdn_port_map|default:"---" }}

    +
    +
    +
    +
    \ No newline at end of file diff --git a/vimhandler/template/config/vmware.html b/vimhandler/template/config/vmware.html new file mode 100644 index 0000000..8e8882f --- /dev/null +++ b/vimhandler/template/config/vmware.html @@ -0,0 +1,148 @@ + \ 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 index 0000000..74204c2 --- /dev/null +++ b/vimhandler/template/config/vmware_show.html @@ -0,0 +1,114 @@ + \ 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 index 0000000..cb0bfa6 --- /dev/null +++ b/vimhandler/template/modal/vim_details.html @@ -0,0 +1,20 @@ + \ No newline at end of file diff --git a/vimhandler/template/vim_create.html b/vimhandler/template/vim_create.html new file mode 100644 index 0000000..477f0be --- /dev/null +++ b/vimhandler/template/vim_create.html @@ -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 }} +
  • Vims
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + +
    +
    +
    + {% csrf_token %} +
    +
    +

    New VIM Account

    +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + + + + + +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    + +
    +
    +
    +
    + + +
    + {% include 'config/openvim.html' %} + {% include 'config/openstack.html' %} + {% include 'config/vmware.html' %} + {% include 'config/aws.html' %} + +
    + +
    +
    +
    + +
    +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + +{% 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 index 0000000..dddde82 --- /dev/null +++ b/vimhandler/template/vim_list.html @@ -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 }} +
  • Vims
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + {% include 'modal/vim_details.html' %} + {% csrf_token %} +
    +
    + +
    +
    +

    Registered VIM

    + +
    +
    + + + + + + + + + + + + + {% for p in datacenters %} + + + + + + + + + + + + + + {% endfor %} + +
    IdNameTypeOperational StateDescriptionActions
    {{ p|get:"_id" }}{{ p|get:"name" }}{{ p|get:"vim_type" }}{{ p|get_sub:"_admin,operationalState"}}{{ p|get_sub:"_admin,description" }} +
    + + +
    +
    +
    +
    +
    + +
    +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + +{% 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 index 0000000..3d34941 --- /dev/null +++ b/vimhandler/template/vim_show.html @@ -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 }} +
  • Vims
  • +{% endblock %} + +{% block content_body %} + {{ block.super }} + +
    +
    + +
    +
    +

    VIM Account details

    +
    + +
    +
    +
    + +
    +
    +
    + +
    +

    {{datacenter.name|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.vim_user|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.vim_url|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.vim_type|default:"---" }}

    +
    +
    + + +
    +
    +
    + +
    +

    {{datacenter.vim_tenant_name|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.description|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.schema_type|default:"---" }}

    +
    +
    +
    + +
    +

    {{datacenter.schema_version|default:"---" }}

    +
    +
    +
    + + +
    + {% 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 %} +
    + +
    +
    + +
    +{% endblock %} + +{% block resource_block %} + {{ block.super }} + + + + +{% 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 index 0000000..79947e6 --- /dev/null +++ b/vimhandler/tests.py @@ -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 index 0000000..d993e0a --- /dev/null +++ b/vimhandler/urls.py @@ -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[0-9a-z-]+)/delete$', views.delete, name='delete'), + url(r'^(?P[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 index 0000000..817a052 --- /dev/null +++ b/vimhandler/views.py @@ -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)