From d8547deb543317e8925b05df8a1866281ec5025f Mon Sep 17 00:00:00 2001 From: Carmelo Romeo Date: Fri, 25 May 2018 15:32:52 +0200 Subject: [PATCH] Code cleaned after Vancouver Demo Change-Id: I9746ec6bec06f8edf2e1b01a320ca3bfab780980 --- .gitignore | 8 + iotronic_ui.egg-info/PKG-INFO | 85 ---- iotronic_ui.egg-info/SOURCES.txt | 88 ---- iotronic_ui.egg-info/dependency_links.txt | 1 - iotronic_ui.egg-info/not-zip-safe | 1 - iotronic_ui.egg-info/pbr.json | 1 - iotronic_ui.egg-info/requires.txt | 6 - iotronic_ui.egg-info/top_level.txt | 1 - iotronic_ui/api/iotronic.py | 111 +++-- iotronic_ui/iot/boards/forms.py | 284 +++++++++++- iotronic_ui/iot/boards/panel.py | 4 + iotronic_ui/iot/boards/tables.py | 56 ++- iotronic_ui/iot/boards/tabs.py | 4 +- .../boards/templates/boards/_attachport.html | 8 + .../boards/templates/boards/_detachport.html | 8 + .../templates/boards/_detail_overview.html | 52 ++- .../templates/boards/_disableservice.html | 8 + .../templates/boards/_enableservice.html | 8 + .../templates/boards/_removeservices.html} | 0 .../boards/templates/boards/attachport.html | 7 + .../boards/templates/boards/detachport.html | 7 + .../templates/boards/disableservice.html | 7 + .../templates/boards/enableservice.html | 7 + .../templates/boards/removeservices.html} | 2 +- iotronic_ui/iot/boards/urls.py | 10 + iotronic_ui/iot/boards/views.py | 433 ++++++++++++------ iotronic_ui/iot/dashboard.py | 1 + iotronic_ui/iot/plugins/forms.py | 113 ++--- iotronic_ui/iot/plugins/panel.py | 5 + iotronic_ui/iot/plugins/tables.py | 24 +- iotronic_ui/iot/plugins/urls.py | 4 +- iotronic_ui/iot/plugins/views.py | 7 - iotronic_ui/iot/services/forms.py | 85 +--- iotronic_ui/iot/services/panel.py | 3 + iotronic_ui/iot/services/tables.py | 22 +- iotronic_ui/iot/services/urls.py | 2 - iotronic_ui/iot/services/views.py | 56 +-- .../iot/static/iot/images/blue-circle.png | Bin 16224 -> 0 bytes .../iot/static/iot/images/green-circle.png | Bin 16582 -> 0 bytes .../static/iot/images/marker-icon-green.png | Bin 1822 -> 0 bytes .../iot/static/iot/images/marker-icon-red.png | Bin 4518 -> 0 bytes .../iot/static/iot/images/marker-icon.png | Bin 1747 -> 0 bytes .../iot/static/iot/images/marker-shadow.png | Bin 797 -> 0 bytes .../iot/static/iot/images/red-circle.png | Bin 16267 -> 0 bytes iotronic_ui/iot/static/iot/js/iot.js | 122 ----- iotronic_ui/iot/static/iot/scss/iot.scss | 7 - 46 files changed, 888 insertions(+), 770 deletions(-) create mode 100644 .gitignore delete mode 100644 iotronic_ui.egg-info/PKG-INFO delete mode 100644 iotronic_ui.egg-info/SOURCES.txt delete mode 100644 iotronic_ui.egg-info/dependency_links.txt delete mode 100644 iotronic_ui.egg-info/not-zip-safe delete mode 100644 iotronic_ui.egg-info/pbr.json delete mode 100644 iotronic_ui.egg-info/requires.txt delete mode 100644 iotronic_ui.egg-info/top_level.txt create mode 100644 iotronic_ui/iot/boards/templates/boards/_attachport.html create mode 100644 iotronic_ui/iot/boards/templates/boards/_detachport.html create mode 100644 iotronic_ui/iot/boards/templates/boards/_disableservice.html create mode 100644 iotronic_ui/iot/boards/templates/boards/_enableservice.html rename iotronic_ui/iot/{services/templates/services/_remove.html => boards/templates/boards/_removeservices.html} (100%) create mode 100644 iotronic_ui/iot/boards/templates/boards/attachport.html create mode 100644 iotronic_ui/iot/boards/templates/boards/detachport.html create mode 100644 iotronic_ui/iot/boards/templates/boards/disableservice.html create mode 100644 iotronic_ui/iot/boards/templates/boards/enableservice.html rename iotronic_ui/iot/{services/templates/services/remove.html => boards/templates/boards/removeservices.html} (72%) delete mode 100644 iotronic_ui/iot/static/iot/images/blue-circle.png delete mode 100644 iotronic_ui/iot/static/iot/images/green-circle.png delete mode 100644 iotronic_ui/iot/static/iot/images/marker-icon-green.png delete mode 100644 iotronic_ui/iot/static/iot/images/marker-icon-red.png delete mode 100644 iotronic_ui/iot/static/iot/images/marker-icon.png delete mode 100644 iotronic_ui/iot/static/iot/images/marker-shadow.png delete mode 100644 iotronic_ui/iot/static/iot/images/red-circle.png delete mode 100644 iotronic_ui/iot/static/iot/js/iot.js delete mode 100644 iotronic_ui/iot/static/iot/scss/iot.scss diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1539061 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.tox +.idea +iotronic_ui.egg-info +build +*.pyc +AUTHORS +Authors +ChangeLog diff --git a/iotronic_ui.egg-info/PKG-INFO b/iotronic_ui.egg-info/PKG-INFO deleted file mode 100644 index df40467..0000000 --- a/iotronic_ui.egg-info/PKG-INFO +++ /dev/null @@ -1,85 +0,0 @@ -Metadata-Version: 1.1 -Name: iotronic-ui -Version: 0.0.0 -Summary: Iotronic plugin for the OpenStack Dashboard -Home-page: http://www.openstack.org/ -Author: OpenStack -Author-email: openstack-dev@lists.openstack.org -License: UNKNOWN -Description: =============================== - IoTronic Panels - =============================== - - Iotronic plugin for the OpenStack Dashboard - - * Free software: Apache license - * Source: http://git.openstack.org/cgit/openstack/iotronic_ui - * Bugs: http://bugs.launchpad.net/None - - Features - -------- - - * TODO - - Enabling in DevStack - -------------------- - - Add this repo as an external repository into your ``local.conf`` file:: - - [[local|localrc]] - enable_plugin iotronic_ui https://github.com/openstack/iotronic_ui - - Manual Installation - ------------------- - - Begin by cloning the Horizon and IoTronic Panels repositories:: - - git clone https://github.com/openstack/horizon - git clone https://github.com/openstack/iotronic_ui - - Create a virtual environment and install Horizon dependencies:: - - cd horizon - python tools/install_venv.py - - Set up your ``local_settings.py`` file:: - - cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py - - Open up the copied ``local_settings.py`` file in your preferred text - editor. You will want to customize several settings: - - - ``OPENSTACK_HOST`` should be configured with the hostname of your - OpenStack server. Verify that the ``OPENSTACK_KEYSTONE_URL`` and - ``OPENSTACK_KEYSTONE_DEFAULT_ROLE`` settings are correct for your - environment. (They should be correct unless you modified your - OpenStack server to change them.) - - Install IoTronic Panels with all dependencies in your virtual environment:: - - tools/with_venv.sh pip install -e ../iotronic_ui/ - - And enable it in Horizon:: - - ln -s ../iotronic_ui/iotronic_ui/enabled/_90_project_iot_panelgroup.py openstack_dashboard/local/enabled - ln -s ../iotronic_ui/iotronic_ui/enabled/_91_project_iot_boardss_panel.py openstack_dashboard/local/enabled - - To run horizon with the newly enabled IoTronic Panels plugin run:: - - ./run_tests.sh --runserver 0.0.0.0:8080 - - to have the application start on port 8080 and the horizon dashboard will be - available in your browser at http://localhost:8080/ - - -Platform: UNKNOWN -Classifier: Environment :: OpenStack -Classifier: Intended Audience :: Information Technology -Classifier: Intended Audience :: System Administrators -Classifier: License :: OSI Approved :: Apache Software License -Classifier: Operating System :: POSIX :: Linux -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.5 diff --git a/iotronic_ui.egg-info/SOURCES.txt b/iotronic_ui.egg-info/SOURCES.txt deleted file mode 100644 index 1e94740..0000000 --- a/iotronic_ui.egg-info/SOURCES.txt +++ /dev/null @@ -1,88 +0,0 @@ -CONTRIBUTING.rst -HACKING.rst -LICENSE -MANIFEST.in -README.rst -babel-django.cfg -babel-djangojs.cfg -manage.py -package.json -requirements.txt -setup.cfg -setup.py -test-requirements.txt -test-shim.js -tox.ini -doc/Makefile -doc/source/conf.py -doc/source/index.rst -doc/source/configuration/index.rst -doc/source/contributor/api.rst -doc/source/contributor/index.rst -doc/source/install/index.rst -iotronic_ui/__init__.py -iotronic_ui.egg-info/PKG-INFO -iotronic_ui.egg-info/SOURCES.txt -iotronic_ui.egg-info/dependency_links.txt -iotronic_ui.egg-info/not-zip-safe -iotronic_ui.egg-info/pbr.json -iotronic_ui.egg-info/requires.txt -iotronic_ui.egg-info/top_level.txt -iotronic_ui/api/iotronic.py -iotronic_ui/enabled/_6000_iot.py -iotronic_ui/enabled/_6010_iot_boards_panel.py -iotronic_ui/enabled/_6020_iot_plugins_panel.py -iotronic_ui/iot/__init__.py -iotronic_ui/iot/dashboard.py -iotronic_ui/iot/boards/__init__.py -iotronic_ui/iot/boards/forms.py -iotronic_ui/iot/boards/panel.py -iotronic_ui/iot/boards/tables.py -iotronic_ui/iot/boards/tabs.py -iotronic_ui/iot/boards/tests.py -iotronic_ui/iot/boards/urls.py -iotronic_ui/iot/boards/views.py -iotronic_ui/iot/boards/templates/boards/_create.html -iotronic_ui/iot/boards/templates/boards/_detail_overview.html -iotronic_ui/iot/boards/templates/boards/_removeplugins.html -iotronic_ui/iot/boards/templates/boards/_update.html -iotronic_ui/iot/boards/templates/boards/create.html -iotronic_ui/iot/boards/templates/boards/index.html -iotronic_ui/iot/boards/templates/boards/removeplugins.html -iotronic_ui/iot/boards/templates/boards/update.html -iotronic_ui/iot/plugins/__init__.py -iotronic_ui/iot/plugins/forms.py -iotronic_ui/iot/plugins/panel.py -iotronic_ui/iot/plugins/tables.py -iotronic_ui/iot/plugins/tabs.py -iotronic_ui/iot/plugins/tests.py -iotronic_ui/iot/plugins/urls.py -iotronic_ui/iot/plugins/views.py -iotronic_ui/iot/plugins/templates/plugins/_call.html -iotronic_ui/iot/plugins/templates/plugins/_create.html -iotronic_ui/iot/plugins/templates/plugins/_detail_overview.html -iotronic_ui/iot/plugins/templates/plugins/_inject.html -iotronic_ui/iot/plugins/templates/plugins/_remove.html -iotronic_ui/iot/plugins/templates/plugins/_start.html -iotronic_ui/iot/plugins/templates/plugins/_stop.html -iotronic_ui/iot/plugins/templates/plugins/_update.html -iotronic_ui/iot/plugins/templates/plugins/call.html -iotronic_ui/iot/plugins/templates/plugins/create.html -iotronic_ui/iot/plugins/templates/plugins/index.html -iotronic_ui/iot/plugins/templates/plugins/inject.html -iotronic_ui/iot/plugins/templates/plugins/remove.html -iotronic_ui/iot/plugins/templates/plugins/start.html -iotronic_ui/iot/plugins/templates/plugins/stop.html -iotronic_ui/iot/plugins/templates/plugins/update.html -iotronic_ui/iot/static/iot/images/blue-circle.png -iotronic_ui/iot/static/iot/images/green-circle.png -iotronic_ui/iot/static/iot/images/marker-icon-green.png -iotronic_ui/iot/static/iot/images/marker-icon-red.png -iotronic_ui/iot/static/iot/images/marker-icon.png -iotronic_ui/iot/static/iot/images/marker-shadow.png -iotronic_ui/iot/static/iot/images/red-circle.png -iotronic_ui/iot/static/iot/js/iot.js -iotronic_ui/iot/static/iot/scss/iot.scss -iotronic_ui/iot/templates/iot/base.html -tools/tox_install.sh -tools/tox_install.sh_ORIG \ No newline at end of file diff --git a/iotronic_ui.egg-info/dependency_links.txt b/iotronic_ui.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/iotronic_ui.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/iotronic_ui.egg-info/not-zip-safe b/iotronic_ui.egg-info/not-zip-safe deleted file mode 100644 index 8b13789..0000000 --- a/iotronic_ui.egg-info/not-zip-safe +++ /dev/null @@ -1 +0,0 @@ - diff --git a/iotronic_ui.egg-info/pbr.json b/iotronic_ui.egg-info/pbr.json deleted file mode 100644 index 34d2e6e..0000000 --- a/iotronic_ui.egg-info/pbr.json +++ /dev/null @@ -1 +0,0 @@ -{"git_version": "4902c1d", "is_release": false} \ No newline at end of file diff --git a/iotronic_ui.egg-info/requires.txt b/iotronic_ui.egg-info/requires.txt deleted file mode 100644 index 1725ef9..0000000 --- a/iotronic_ui.egg-info/requires.txt +++ /dev/null @@ -1,6 +0,0 @@ -pbr!=2.1.0,>=2.0.0 -Babel!=2.4.0,>=2.3.4 -Django<2.0,>=1.8 -django-babel>=0.5.1 -django-compressor>=2.0 -django-pyscss>=2.0.2 diff --git a/iotronic_ui.egg-info/top_level.txt b/iotronic_ui.egg-info/top_level.txt deleted file mode 100644 index 7d4fe56..0000000 --- a/iotronic_ui.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -iotronic_ui diff --git a/iotronic_ui/api/iotronic.py b/iotronic_ui/api/iotronic.py index cf2c825..8957f5f 100644 --- a/iotronic_ui/api/iotronic.py +++ b/iotronic_ui/api/iotronic.py @@ -43,14 +43,12 @@ def iotronicclient(request): # BOARD MANAGEMENT def board_list(request, status=None, detail=None, project=None): """List boards.""" - boards = iotronicclient(request).board.list(status, detail, project) - return boards + return iotronicclient(request).board.list(status, detail, project) def board_get(request, board_id, fields): """Get board info.""" - board = iotronicclient(request).board.get(board_id, fields) - return board + return iotronicclient(request).board.get(board_id, fields) def board_create(request, code, mobile, location, type, name): @@ -60,30 +58,26 @@ def board_create(request, code, mobile, location, type, name): "location": location, "type": type, "name": name} - board = iotronicclient(request).board.create(**params) - return board + iotronicclient(request).board.create(**params) def board_update(request, board_id, patch): """Update board.""" - board = iotronicclient(request).board.update(board_id, patch) - return board + iotronicclient(request).board.update(board_id, patch) def board_delete(request, board_id): """Delete board.""" - board = iotronicclient(request).board.delete(board_id) - return board + iotronicclient(request).board.delete(board_id) # PLUGIN MANAGEMENT (Cloud Side) def plugin_list(request, detail=None, project=None, with_public=False, all_plugins=False): """List plugins.""" - plugins = iotronicclient(request).plugin.list(detail, project, \ - with_public=with_public, \ - all_plugins=all_plugins) - return plugins + return iotronicclient(request).plugin.list(detail, project, + with_public=with_public, + all_plugins=all_plugins) def plugin_get(request, plugin_id, fields): @@ -99,53 +93,43 @@ def plugin_create(request, name, public, callable, code, parameters): "callable": callable, "code": code, "parameters": parameters} - plugin = iotronicclient(request).plugin.create(**params) - return plugin + iotronicclient(request).plugin.create(**params) def plugin_update(request, plugin_id, patch): """Update plugin.""" - plugin = iotronicclient(request).plugin.update(plugin_id, patch) - return plugin + iotronicclient(request).plugin.update(plugin_id, patch) def plugin_delete(request, plugin_id): """Delete plugin.""" - plugin = iotronicclient(request).plugin.delete(plugin_id) - return plugin + return iotronicclient(request).plugin.delete(plugin_id) # PLUGIN MANAGEMENT (Board Side) def plugin_inject(request, board_id, plugin_id, onboot): """Inject plugin on board(s).""" - plugin = iotronicclient(request).plugin_injection. \ - plugin_inject(board_id, \ - plugin_id, \ - onboot) - return plugin + return iotronicclient(request).plugin_injection.plugin_inject(board_id, + plugin_id, + onboot) def plugin_action(request, board_id, plugin_id, action, params={}): """Start/Stop/Call actions on board(s).""" - plugin = iotronicclient(request).plugin_injection. \ - plugin_action(board_id, - plugin_id, - action, - params) - return plugin + return iotronicclient(request).plugin_injection.plugin_action( + board_id, plugin_id, action, params) def plugin_remove(request, board_id, plugin_id): - """Inject plugin on board(s).""" - plugin = iotronicclient(request).plugin_injection. \ - plugin_remove(board_id, plugin_id) - return plugin + """Remove plugin from board.""" + iotronicclient(request).plugin_injection.plugin_remove(board_id, + plugin_id) def plugins_on_board(request, board_id): """Plugins on board.""" - plugins = iotronicclient(request).plugin_injection. \ - plugins_on_board(board_id) + plugins = iotronicclient(request).plugin_injection.plugins_on_board( + board_id) detailed_plugins = [] # fields = {"name", "public", "callable"} @@ -161,14 +145,12 @@ def plugins_on_board(request, board_id): # SERVICE MANAGEMENT def service_list(request, detail=None): """List services.""" - services = iotronicclient(request).service.list(detail) - return services + return iotronicclient(request).service.list(detail) def service_get(request, service_id, fields): """Get service info.""" - service = iotronicclient(request).service.get(service_id, fields) - return service + return iotronicclient(request).service.get(service_id, fields) def service_create(request, name, port, protocol): @@ -176,36 +158,34 @@ def service_create(request, name, port, protocol): params = {"name": name, "port": port, "protocol": protocol} - service = iotronicclient(request).service.create(**params) - return service + iotronicclient(request).service.create(**params) def service_update(request, service_id, patch): """Update service.""" - service = iotronicclient(request).service.update(service_id, patch) - return service + iotronicclient(request).service.update(service_id, patch) def service_delete(request, service_id): """Delete service.""" - service = iotronicclient(request).service.delete(service_id) - return service + iotronicclient(request).service.delete(service_id) def services_on_board(request, board_id, detail=False): """List services on board.""" - services = iotronicclient(request).exposed_service \ - .services_on_board(board_id) + services = iotronicclient(request).exposed_service.services_on_board( + board_id) if detail: detailed_services = [] fields = {"name", "port", "protocol"} for service in services: - details = iotronicclient(request). \ - service.get(service._info["service"], fields) + details = iotronicclient(request).service.get( + service._info["service"], fields) - detailed_services.append({"name": details._info["name"], + detailed_services.append({"uuid": service._info["service"], + "name": details._info["name"], "public_port": service._info["public_port"], "port": details._info["port"], @@ -219,14 +199,29 @@ def services_on_board(request, board_id, detail=False): def service_action(request, board_id, service_id, action): """Action on service.""" - service_action = iotronicclient(request).exposed_service. \ - service_action(board_id, service_id, action) - return service_action + return iotronicclient(request).exposed_service.service_action(board_id, + service_id, + action) def restore_services(request, board_id): """Restore services.""" - service_restore = iotronicclient(request).exposed_service. \ - restore_services(board_id) - return service_restore + return iotronicclient(request).exposed_service.restore_services(board_id) + +# PORTS MANAGEMENT +def port_list(request, board_id): + """Get ports attached to a board.""" + return iotronicclient(request).port.list() + + +def attach_port(request, board_id, network_id, subnet_id): + """Attach port to a subnet for a board.""" + return iotronicclient(request).portonboard.attach_port(board_id, + network_id, + subnet_id) + + +def detach_port(request, board_id, port_id): + """Detach port from the board.""" + iotronicclient(request).portonboard.detach_port(board_id, port_id) diff --git a/iotronic_ui/iot/boards/forms.py b/iotronic_ui/iot/boards/forms.py index 8e4ba45..e128360 100644 --- a/iotronic_ui/iot/boards/forms.py +++ b/iotronic_ui/iot/boards/forms.py @@ -67,12 +67,13 @@ class CreateBoardForm(forms.SelfHandlingForm): "longitude": str(data["longitude"]), "altitude": str(data["altitude"])}] - board = iotronic.board_create(request, data["code"], - data["mobile"], data["location"], - data["type"], data["name"]) - messages.success(request, _("Board created successfully.")) + iotronic.board_create(request, data["code"], + data["mobile"], data["location"], + data["type"], data["name"]) + + messages.success(request, _("Board created successfully.")) + return True - return board except Exception: exceptions.handle(request, _('Unable to create board.')) @@ -82,49 +83,54 @@ class UpdateBoardForm(forms.SelfHandlingForm): name = forms.CharField(label=_("Board Name")) mobile = forms.BooleanField(label=_("Mobile"), required=False) + """ latitude = forms.FloatField(label=_("Latitude")) longitude = forms.FloatField(label=_("Longitude")) altitude = forms.FloatField(label=_("Altitude")) + """ def __init__(self, *args, **kwargs): super(UpdateBoardForm, self).__init__(*args, **kwargs) - # LOG.debug("MELO INITIAL: %s", kwargs["initial"]) + # LOG.debug("INITIAL: %s", kwargs["initial"]) - LOG.debug("MELO Manager: %s", policy.check((("iot", "iot_manager"),), - self.request)) - LOG.debug("MELO Admin: %s", policy.check((("iot", "iot_admin"),), - self.request)) + # LOG.debug("Manager: %s", policy.check((("iot", "iot_manager"),), + # self.request)) + # LOG.debug("Admin: %s", policy.check((("iot", "iot_admin"),), + # self.request)) # Admin if policy.check((("iot", "iot:update_boards"),), self.request): - # LOG.debug("MELO ADMIN") + # LOG.debug("ADMIN") pass # Manager or Admin of the iot project elif (policy.check((("iot", "iot_manager"),), self.request) or policy.check((("iot", "iot_admin"),), self.request)): - # LOG.debug("MELO NO-edit IOT ADMIN") + # LOG.debug("NO-edit IOT ADMIN") pass # Other users else: if self.request.user.id != kwargs["initial"]["owner"]: - # LOG.debug("MELO IMMUTABLE FIELDS") + # LOG.debug("IMMUTABLE FIELDS") self.fields["name"].widget.attrs = {'readonly': 'readonly'} self.fields["mobile"].widget.attrs = {'disabled': 'disabled'} + """ self.fields["latitude"].widget.attrs = {'readonly': 'readonly'} self.fields["longitude"].widget.attrs = {'readonly': 'readonly'} self.fields["altitude"].widget.attrs = {'readonly': 'readonly'} + """ def handle(self, request, data): try: + """ data["location"] = [{"latitude": str(data["latitude"]), "longitude": str(data["longitude"]), "altitude": str(data["altitude"])}] @@ -132,13 +138,196 @@ class UpdateBoardForm(forms.SelfHandlingForm): {"name": data["name"], "mobile": data["mobile"], "location": data["location"]}) - + """ + iotronic.board_update(request, data["uuid"], + {"name": data["name"], + "mobile": data["mobile"]}) messages.success(request, _("Board updated successfully.")) return True + except Exception: exceptions.handle(request, _('Unable to update board.')) +class EnableServiceForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Board Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + service_list = forms.MultipleChoiceField( + label=_("Services List"), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', + 'data-slug': 'slug-select-services'}), + help_text=_("Add available services from this pool") + ) + + def __init__(self, *args, **kwargs): + super(EnableServiceForm, self).__init__(*args, **kwargs) + self.fields["service_list"].choices = kwargs["initial"]["service_list"] + + def handle(self, request, data): + + counter = 0 + for service in data["service_list"]: + try: + action = iotronic.service_action(request, data["uuid"], + service, "ServiceEnable") + + # message_text = "Service(s) enabled successfully." + message_text = action + messages.success(request, _(message_text)) + + if counter != len(data["service_list"]) - 1: + counter += 1 + else: + return True + + except Exception: + message_text = "Unable to enable service." + exceptions.handle(request, _(message_text)) + + +class DisableServiceForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Board Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + service_list = forms.MultipleChoiceField( + label=_("Services List"), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', + 'data-slug': 'slug-select-services'}), + help_text=_("Select services to disable from this pool") + ) + + def __init__(self, *args, **kwargs): + super(DisableServiceForm, self).__init__(*args, **kwargs) + self.fields["service_list"].choices = kwargs["initial"]["service_list"] + + def handle(self, request, data): + + counter = 0 + for service in data["service_list"]: + try: + action = iotronic.service_action(request, data["uuid"], + service, "ServiceDisable") + + # message_text = "Service(s) disabled successfully." + message_text = action + messages.success(request, _(message_text)) + + if counter != len(data["service_list"]) - 1: + counter += 1 + else: + return True + + except Exception: + message_text = "Unable to disable service." + exceptions.handle(request, _(message_text)) + + +class AttachPortForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Board Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + networks_list = forms.ChoiceField( + label=_("Networks List"), + help_text=_("Select network:subnet from the list") + ) + + def __init__(self, *args, **kwargs): + + super(AttachPortForm, self).__init__(*args, **kwargs) + + net_choices = kwargs["initial"]["networks_list"] + self.fields["networks_list"].choices = net_choices + + def handle(self, request, data): + array = data["networks_list"].split(':') + LOG.debug(array) + network_id = array[0] + subnet_id = array[1] + + try: + attach = iotronic.attach_port(request, data["uuid"], + network_id, subnet_id) + + # LOG.debug("ATTACH: %s", attach) + ip = attach._info["ip"] + + message_text = "Attached port to ip " + str(ip) + \ + " on board " + str(data["name"]) + \ + " completed successfully" + messages.success(request, _(message_text)) + return True + + except Exception: + message_text = "Unable to attach port on board " + \ + str(data["name"]) + + exceptions.handle(request, _(message_text)) + + +class DetachPortForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Board Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + port_list = forms.MultipleChoiceField( + label=_("Ports List"), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', 'data-slug': 'slug-detacj-ports'}), + help_text=_("Select one or more of the following attached ports") + ) + + def __init__(self, *args, **kwargs): + + super(DetachPortForm, self).__init__(*args, **kwargs) + self.fields["port_list"].choices = kwargs["initial"]["ports"] + + def handle(self, request, data): + # LOG.debug("DATA: %s %s", data, len(data["port_list"])) + + counter = 0 + + for port in data["port_list"]: + try: + iotronic.detach_port(request, data["uuid"], port) + + message_text = "Detach port " + str(port) + " from board " + \ + str(data["name"]) + " completed successfully" + messages.success(request, _(message_text)) + + if counter != len(data["port_list"]) - 1: + counter += 1 + else: + return True + + except Exception: + message_text = "Unable to detach port " + str(port) + \ + " from board " + str(data["name"]) + + exceptions.handle(request, _(message_text)) + + class RemovePluginsForm(forms.SelfHandlingForm): uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput) @@ -159,11 +348,7 @@ class RemovePluginsForm(forms.SelfHandlingForm): super(RemovePluginsForm, self).__init__(*args, **kwargs) # input=kwargs.get('initial',{}) - - boardslist_length = len(kwargs["initial"]["board_list"]) - self.fields["plugin_list"].choices = kwargs["initial"]["plugin_list"] - self.fields["plugin_list"].max_length = boardslist_length def handle(self, request, data): @@ -174,13 +359,8 @@ class RemovePluginsForm(forms.SelfHandlingForm): if key == plugin: try: - board = None + iotronic.plugin_remove(request, data["uuid"], key) - # LOG.debug('INJECT: %s %s', plugin, value) - # board = iotronic.plugin_create(request, data["name"], - # data["public"], - # data["callable"], - # data["code"]) message_text = "Plugin " + str(value) + \ " removed successfully." messages.success(request, _(message_text)) @@ -188,10 +368,64 @@ class RemovePluginsForm(forms.SelfHandlingForm): if counter != len(data["plugin_list"]) - 1: counter += 1 else: - return board + return True + except Exception: message_text = "Unable to remove plugin " \ + str(value) + "." exceptions.handle(request, _(message_text)) break + + +class RemoveServicesForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Board Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + service_list = forms.MultipleChoiceField( + label=_("Services List"), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', + 'data-slug': 'slug-remove-services'}), + help_text=_("Select services in this pool") + ) + + def __init__(self, *args, **kwargs): + + super(RemoveServicesForm, self).__init__(*args, **kwargs) + # input=kwargs.get('initial',{}) + self.fields["service_list"].choices = kwargs["initial"]["service_list"] + + def handle(self, request, data): + + counter = 0 + + for service in data["service_list"]: + for key, value in self.fields["service_list"].choices: + if key == service: + + try: + disable = iotronic.service_action(request, + data["uuid"], + key, + "ServiceDisable") + + message_text = disable + messages.success(request, _(message_text)) + + if counter != len(data["service_list"]) - 1: + counter += 1 + else: + return True + + except Exception: + message_text = "Unable to disable service " \ + + str(value) + "." + exceptions.handle(request, _(message_text)) + + break diff --git a/iotronic_ui/iot/boards/panel.py b/iotronic_ui/iot/boards/panel.py index beb3ef9..fd23096 100644 --- a/iotronic_ui/iot/boards/panel.py +++ b/iotronic_ui/iot/boards/panel.py @@ -15,6 +15,7 @@ from django.utils.translation import ugettext_lazy as _ import horizon # from openstack_dashboard.api import keystone +from iotronic_ui.iot import dashboard class Boards(horizon.Panel): @@ -31,3 +32,6 @@ class Boards(horizon.Panel): return False return super(Roles, self).can_access(context) """ + + +dashboard.Iot.register(Boards) diff --git a/iotronic_ui/iot/boards/tables.py b/iotronic_ui/iot/boards/tables.py index c944caf..afee155 100644 --- a/iotronic_ui/iot/boards/tables.py +++ b/iotronic_ui/iot/boards/tables.py @@ -64,13 +64,56 @@ class RestoreServices(tables.BatchAction): api.iotronic.restore_services(request, board_id) +class EnableServiceLink(tables.LinkAction): + name = "enableservice" + verbose_name = _("Enable Service(s)") + url = "horizon:iot:boards:enableservice" + classes = ("ajax-modal",) + # icon = "plus" + # policy_rules = (("iot", "iot:service_action"),) + + +class DisableServiceLink(tables.LinkAction): + name = "disableservice" + verbose_name = _("Disable Service(s)") + url = "horizon:iot:boards:disableservice" + classes = ("ajax-modal",) + # icon = "plus" + # policy_rules = (("iot", "iot:service_action"),) + + class RemovePluginsLink(tables.LinkAction): name = "removeplugins" verbose_name = _("Remove Plugin(s)") url = "horizon:iot:boards:removeplugins" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:remove_plugins"),) + + +class RemoveServicesLink(tables.LinkAction): + name = "removeservices" + verbose_name = _("Remove Service(s)") + url = "horizon:iot:boards:removeservices" + classes = ("ajax-modal",) + icon = "plus" + # policy_rules = (("iot", "iot:remove_services"),) + + +class AttachPortLink(tables.LinkAction): + name = "attachport" + verbose_name = _("Attach Port") + url = "horizon:iot:boards:attachport" + classes = ("ajax-modal",) + icon = "plus" + + +class DetachPortLink(tables.LinkAction): + name = "detachport" + verbose_name = _("Detach Port") + url = "horizon:iot:boards:detachport" + classes = ("ajax-modal",) + icon = "plus" class DeleteBoardsAction(tables.DeleteAction): @@ -117,6 +160,7 @@ class BoardFilterAction(tables.FilterAction): return [board for board in boards if q in board.name.lower()] + def show_services(board_info): template_name = 'iot/boards/_cell_services.html' context = board_info._info @@ -145,7 +189,9 @@ class BoardsTable(tables.DataTable): class Meta(object): name = "boards" verbose_name = _("boards") - row_actions = (EditBoardLink, RestoreServices, - RemovePluginsLink, DeleteBoardsAction) - table_actions = (BoardFilterAction, CreateBoardLink, - RestoreServices, DeleteBoardsAction) + row_actions = (EditBoardLink, EnableServiceLink, DisableServiceLink, + RestoreServices, AttachPortLink, DetachPortLink, + RemovePluginsLink, RemoveServicesLink, + DeleteBoardsAction) + table_actions = (BoardFilterAction, CreateBoardLink, + DeleteBoardsAction) diff --git a/iotronic_ui/iot/boards/tabs.py b/iotronic_ui/iot/boards/tabs.py index 07a5528..4bdef2b 100644 --- a/iotronic_ui/iot/boards/tabs.py +++ b/iotronic_ui/iot/boards/tabs.py @@ -21,7 +21,7 @@ LOG = logging.getLogger(__name__) """ import inspect -LOG.debug('MELO CLASSES: %s', +LOG.debug('CLASSES: %s', inspect.getmembers(tabs, predicate=inspect.isclass)) """ @@ -34,12 +34,14 @@ class OverviewTab(tabs.Tab): def get_context_data(self, request): coordinates = self.tab_group.kwargs['board'].__dict__['location'][0] + ports = self.tab_group.kwargs['board']._info['ports'] services = self.tab_group.kwargs['board']._info['services'] plugins = self.tab_group.kwargs['board']._info['plugins'] return {"board": self.tab_group.kwargs['board'], "coordinates": coordinates, "services": services, + "ports": ports, "plugins": plugins, "is_superuser": request.user.is_superuser} diff --git a/iotronic_ui/iot/boards/templates/boards/_attachport.html b/iotronic_ui/iot/boards/templates/boards/_attachport.html new file mode 100644 index 0000000..0fc9d03 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/_attachport.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Attach port to the selected board." %}

+{% endblock %} + diff --git a/iotronic_ui/iot/boards/templates/boards/_detachport.html b/iotronic_ui/iot/boards/templates/boards/_detachport.html new file mode 100644 index 0000000..fe52c60 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/_detachport.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Detach one or more attached ports from the board." %}

+{% endblock %} + diff --git a/iotronic_ui/iot/boards/templates/boards/_detail_overview.html b/iotronic_ui/iot/boards/templates/boards/_detail_overview.html index 60afed2..f7e8e27 100644 --- a/iotronic_ui/iot/boards/templates/boards/_detail_overview.html +++ b/iotronic_ui/iot/boards/templates/boards/_detail_overview.html @@ -22,26 +22,50 @@
{{ board.mobile }}
{% trans "Extra" %}
{{ board.extra }}
-
{% trans "Services" %}
- {% if services %} + + +

{% trans "Ports" %}

+
+
+ {% if ports %} + {% for port in ports %} +
{{ port.VIF_name }}
+
{{ port.ip }}
+ {% endfor %} + {% else %} +
--
+ {% endif %} +
+ +

{% trans "Services" %}

+
+
+ {% if services %} {% for service in services %} -
{{ service.name }} [{{ service.protocol }}] {{ service.port }} --> {{ service.public_port }}
+
{{ service.name }} [{{ service.protocol }}] {{ service.port }}
+
{{ service.public_port }}
{% endfor %} - {% else %} + {% else %}
--
- {% endif %} -
{% trans "Plugins" %}
- {% if plugins %} - {% for plugin in plugins %} -
{{ plugin.name }}
- {% endfor %} - {% else %} -
--
- {% endif %} + {% endif %} +
+ +

{% trans "Plugins" %}

+
+
+ {% if plugins %} + {% for plugin in plugins %} +
{{ plugin.name }}
+
{{ plugin.id }}
+ {% endfor %} + {% else %} +
--
+ {% endif %}
+ diff --git a/iotronic_ui/iot/boards/templates/boards/_disableservice.html b/iotronic_ui/iot/boards/templates/boards/_disableservice.html new file mode 100644 index 0000000..21138ce --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/_disableservice.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Disable service(s) on the selected board." %}

+{% endblock %} + diff --git a/iotronic_ui/iot/boards/templates/boards/_enableservice.html b/iotronic_ui/iot/boards/templates/boards/_enableservice.html new file mode 100644 index 0000000..ec9e3be --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/_enableservice.html @@ -0,0 +1,8 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Enable service(s) on the selected board." %}

+{% endblock %} + diff --git a/iotronic_ui/iot/services/templates/services/_remove.html b/iotronic_ui/iot/boards/templates/boards/_removeservices.html similarity index 100% rename from iotronic_ui/iot/services/templates/services/_remove.html rename to iotronic_ui/iot/boards/templates/boards/_removeservices.html diff --git a/iotronic_ui/iot/boards/templates/boards/attachport.html b/iotronic_ui/iot/boards/templates/boards/attachport.html new file mode 100644 index 0000000..72b3c94 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/attachport.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Attach" %}{% endblock %} + +{% block main %} + {% include 'iot/boards/_attachport.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/boards/templates/boards/detachport.html b/iotronic_ui/iot/boards/templates/boards/detachport.html new file mode 100644 index 0000000..aa2e4a1 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/detachport.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Detach" %}{% endblock %} + +{% block main %} + {% include 'iot/boards/_detachport.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/boards/templates/boards/disableservice.html b/iotronic_ui/iot/boards/templates/boards/disableservice.html new file mode 100644 index 0000000..4280e56 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/disableservice.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Disable" %}{% endblock %} + +{% block main %} + {% include 'iot/boards/_disableservice.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/boards/templates/boards/enableservice.html b/iotronic_ui/iot/boards/templates/boards/enableservice.html new file mode 100644 index 0000000..968caf4 --- /dev/null +++ b/iotronic_ui/iot/boards/templates/boards/enableservice.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Enable" %}{% endblock %} + +{% block main %} + {% include 'iot/boards/_enableservice.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/services/templates/services/remove.html b/iotronic_ui/iot/boards/templates/boards/removeservices.html similarity index 72% rename from iotronic_ui/iot/services/templates/services/remove.html rename to iotronic_ui/iot/boards/templates/boards/removeservices.html index d1addca..0c2f1a1 100644 --- a/iotronic_ui/iot/services/templates/services/remove.html +++ b/iotronic_ui/iot/boards/templates/boards/removeservices.html @@ -3,5 +3,5 @@ {% block title %}{% trans "Remove service(s)." %}{% endblock %} {% block main %} - {% include 'iot/services/_remove.html' %} + {% include 'iot/boards/_removeservices.html' %} {% endblock %} diff --git a/iotronic_ui/iot/boards/urls.py b/iotronic_ui/iot/boards/urls.py index 7349a04..0348d95 100644 --- a/iotronic_ui/iot/boards/urls.py +++ b/iotronic_ui/iot/boards/urls.py @@ -22,6 +22,16 @@ urlpatterns = [ name='update'), url(r'^(?P[^/]+)/removeplugins/$', views.RemovePluginsView.as_view(), name='removeplugins'), + url(r'^(?P[^/]+)/removeservices/$', + views.RemoveServicesView.as_view(), name='removeservices'), + url(r'^(?P[^/]+)/enableservice/$', + views.EnableServiceView.as_view(), name='enableservice'), + url(r'^(?P[^/]+)/disableservice/$', + views.DisableServiceView.as_view(), name='disableservice'), + url(r'^(?P[^/]+)/attachport/$', + views.AttachPortView.as_view(), name='attachport'), + url(r'^(?P[^/]+)/detachport/$', + views.DetachPortView.as_view(), name='detachport'), url(r'^(?P[^/]+)/detail/$', views.BoardDetailView.as_view(), name='detail'), ] diff --git a/iotronic_ui/iot/boards/views.py b/iotronic_ui/iot/boards/views.py index df8e3af..de6a6bf 100644 --- a/iotronic_ui/iot/boards/views.py +++ b/iotronic_ui/iot/boards/views.py @@ -23,7 +23,8 @@ from horizon import tables from horizon import tabs from horizon.utils import memoized -from openstack_dashboard.api import iotronic +# from openstack_dashboard.api import iotronic +from openstack_dashboard import api from openstack_dashboard import policy from iotronic_ui.iot.boards import forms as project_forms @@ -42,25 +43,10 @@ class IndexView(tables.DataTableView): def get_data(self): boards = [] - # FROM - """ - if policy.check((("identity", "identity:list_roles"),), self.request): - try: - boards = iotronic.board_list(self.request, None, None) - # LOG.debug('IOT BOARDS: %s', boards) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve boards list.')) - else: - msg = _("Insufficient privilege level to view boards information.") - messages.info(self.request, msg) - """ - - # TO # Admin if policy.check((("iot", "iot:list_all_boards"),), self.request): try: - boards = iotronic.board_list(self.request, None, None) + boards = api.iotronic.board_list(self.request, None, None) except Exception: exceptions.handle(self.request, @@ -69,7 +55,7 @@ class IndexView(tables.DataTableView): # Admin_iot_project elif policy.check((("iot", "iot:list_project_boards"),), self.request): try: - boards = iotronic.board_list(self.request, None, None) + boards = api.iotronic.board_list(self.request, None, None) except Exception: exceptions.handle(self.request, @@ -78,14 +64,16 @@ class IndexView(tables.DataTableView): # Other users else: try: - boards = iotronic.board_list(self.request, None, None) + boards = api.iotronic.board_list(self.request, None, None) except Exception: exceptions.handle(self.request, _('Unable to retrieve user boards list.')) for board in boards: - board_services = iotronic.services_on_board(self.request, board.uuid, True) + board_services = api.iotronic.services_on_board(self.request, + board.uuid, + True) # board.__dict__.update(dict(services=board_services)) board._info.update(dict(services=board_services)) @@ -116,8 +104,9 @@ class UpdateView(forms.ModalFormView): @memoized.memoized_method def get_object(self): try: - return iotronic.board_get(self.request, self.kwargs['board_id'], - None) + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) except Exception: redirect = reverse("horizon:iot:boards:index") exceptions.handle(self.request, @@ -143,12 +132,226 @@ class UpdateView(forms.ModalFormView): 'altitude': location["altitude"]} +class EnableServiceView(forms.ModalFormView): + template_name = 'iot/boards/enableservice.html' + modal_header = _("Enable Service(s)") + form_id = "service_enable_form" + form_class = project_forms.EnableServiceForm + submit_label = _("Enable") + # submit_url = reverse_lazy("horizon:iot:boards:enableservice") + submit_url = "horizon:iot:boards:enableservice" + success_url = reverse_lazy('horizon:iot:boards:index') + page_title = _("Action") + + @memoized.memoized_method + def get_object(self): + try: + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) + + except Exception: + redirect = reverse("horizon:iot:boards:index") + exceptions.handle(self.request, + _('Unable to get board information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(EnableServiceView, self).get_context_data(**kwargs) + args = (self.get_object().uuid,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + board = self.get_object() + + # Populate available services + cloud_services = api.iotronic.service_list(self.request, None) + board_services = api.iotronic.services_on_board(self.request, + board.uuid, + True) + + service_list = [] + + for cloud_service in cloud_services: + + if len(board_services) == 0: + service_list.append((cloud_service._info["uuid"], + _(cloud_service._info["name"]))) + else: + counter = 0 + for board_service in board_services: + if board_service["uuid"] == cloud_service._info["uuid"]: + break + elif counter != len(board_services) - 1: + counter += 1 + else: + service_list.append((cloud_service._info["uuid"], + _(cloud_service._info["name"]))) + + return {'uuid': board.uuid, + 'name': board.name, + 'service_list': service_list} + + +class DisableServiceView(forms.ModalFormView): + template_name = 'iot/boards/disableservice.html' + modal_header = _("Disable Service(s)") + form_id = "service_disable_form" + form_class = project_forms.DisableServiceForm + submit_label = _("Disable") + # submit_url = reverse_lazy("horizon:iot:boards:disableservice") + submit_url = "horizon:iot:boards:disableservice" + success_url = reverse_lazy('horizon:iot:boards:index') + page_title = _("Action") + + @memoized.memoized_method + def get_object(self): + try: + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) + + except Exception: + redirect = reverse("horizon:iot:boards:index") + exceptions.handle(self.request, + _('Unable to get board information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(DisableServiceView, self).get_context_data(**kwargs) + args = (self.get_object().uuid,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + board = self.get_object() + + # Populate available services + cloud_services = api.iotronic.service_list(self.request, None) + board_services = api.iotronic.services_on_board(self.request, + board.uuid, + True) + + service_list = [] + + for cloud_service in cloud_services: + for board_service in board_services: + if board_service["uuid"] == cloud_service._info["uuid"]: + service_list.append((cloud_service._info["uuid"], + _(cloud_service._info["name"]))) + + return {'uuid': board.uuid, + 'name': board.name, + 'service_list': service_list} + + +class AttachPortView(forms.ModalFormView): + template_name = 'iot/boards/attachport.html' + modal_header = _("Attach") + form_id = "attach_boardport_form" + form_class = project_forms.AttachPortForm + submit_label = _("Attach") + # submit_url = reverse_lazy("horizon:iot:boards:attachport") + submit_url = "horizon:iot:boards:attachport" + success_url = reverse_lazy('horizon:iot:boards:index') + page_title = _("Attach port") + + @memoized.memoized_method + def get_object(self): + try: + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) + except Exception: + redirect = reverse("horizon:iot:boards:index") + exceptions.handle(self.request, + _('Unable to get board information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(AttachPortView, self).get_context_data(**kwargs) + args = (self.get_object().uuid,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + board = self.get_object() + + # Populate networks + networks = api.neutron.network_list(self.request) + net_choices = [] + + for net in networks: + for subnet in net["subnets"]: + net_choices.append((net["id"] + ':' + subnet["id"], + _(net["name"] + ':' + subnet["name"]))) + + return {'uuid': board.uuid, + 'name': board.name, + 'networks_list': net_choices} + + +class DetachPortView(forms.ModalFormView): + template_name = 'iot/boards/detachport.html' + modal_header = _("Detach") + form_id = "detach_boardport_form" + form_class = project_forms.DetachPortForm + submit_label = _("Detach") + # submit_url = reverse_lazy("horizon:iot:boards:detachport") + submit_url = "horizon:iot:boards:detachport" + success_url = reverse_lazy('horizon:iot:boards:index') + page_title = _("Detach port") + + @memoized.memoized_method + def get_object(self): + try: + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) + except Exception: + redirect = reverse("horizon:iot:boards:index") + exceptions.handle(self.request, + _('Unable to get board information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(DetachPortView, self).get_context_data(**kwargs) + args = (self.get_object().uuid,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + board = self.get_object() + + ports = api.iotronic.port_list(self.request, board.uuid) + + # TO BE REMOVED (change it once the port_list per board is + # completed and tested ! + # ################################################################ + # LOG.debug("PORTS: %s", ports) + + filtered_ports = [] + for port in ports: + if port._info["board_uuid"] == board.uuid: + filtered_ports.append((port._info["uuid"], + _(port._info["ip"]))) + + ports = filtered_ports + # ################################################################ + + # Populate board ports + return {'uuid': board.uuid, + 'name': board.name, + 'ports': ports} + + class RemovePluginsView(forms.ModalFormView): template_name = 'iot/boards/removeplugins.html' modal_header = _("Remove Plugins from board") form_id = "remove_boardplugins_form" form_class = project_forms.RemovePluginsForm - submit_label = _("Remove Plugins from board") + submit_label = _("Remove") # submit_url = reverse_lazy("horizon:iot:boards:removeplugins") submit_url = "horizon:iot:boards:removeplugins" success_url = reverse_lazy('horizon:iot:boards:index') @@ -157,8 +360,9 @@ class RemovePluginsView(forms.ModalFormView): @memoized.memoized_method def get_object(self): try: - return iotronic.board_get(self.request, self.kwargs['board_id'], - None) + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) except Exception: redirect = reverse("horizon:iot:boards:index") exceptions.handle(self.request, @@ -176,49 +380,74 @@ class RemovePluginsView(forms.ModalFormView): # Populate plugins # TO BE DONE.....filter by available on this board!!! - # plugins = iotronic.plugin_list(self.request, None, None) - plugins = iotronic.plugins_on_board(self.request, board.uuid) + # plugins = api.iotronic.plugin_list(self.request, None, None) + plugins = api.iotronic.plugins_on_board(self.request, board.uuid) - plugins.sort(key=lambda b: b.name) + plugins.sort(key=lambda b: b["name"]) plugin_list = [] for plugin in plugins: - plugin_list.append((plugin.uuid, _(plugin.name))) + plugin_list.append((plugin["id"], _(plugin["name"]))) return {'uuid': board.uuid, 'name': board.name, 'plugin_list': plugin_list} +class RemoveServicesView(forms.ModalFormView): + template_name = 'iot/boards/removeservices.html' + modal_header = _("Remove Services from board") + form_id = "remove_boardservices_form" + form_class = project_forms.RemoveServicesForm + submit_label = _("Remove") + # submit_url = reverse_lazy("horizon:iot:boards:removeservices") + submit_url = "horizon:iot:boards:removeservices" + success_url = reverse_lazy('horizon:iot:boards:index') + page_title = _("Remove Services from board") + + @memoized.memoized_method + def get_object(self): + try: + return api.iotronic.board_get(self.request, + self.kwargs['board_id'], + None) + except Exception: + redirect = reverse("horizon:iot:boards:index") + exceptions.handle(self.request, + _('Unable to get board information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(RemoveServicesView, self).get_context_data(**kwargs) + args = (self.get_object().uuid,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + board = self.get_object() + + # Populate services + services = api.iotronic.services_on_board(self.request, + board.uuid, + True) + services.sort(key=lambda b: b["name"]) + + service_list = [] + for service in services: + service_list.append((service["uuid"], _(service["name"]))) + + return {'uuid': board.uuid, + 'name': board.name, + 'service_list': service_list} + + class DetailView(tabs.TabView): - # FROM - """ - tab_group_class = project_tabs.InstanceDetailTabs - template_name = 'horizon/common/_detail.html' - redirect_url = 'horizon:project:instances:index' - page_title = "{{ instance.name|default:instance.id }}" - image_url = 'horizon:project:images:images:detail' - volume_url = 'horizon:project:volumes:volumes:detail' - """ - # TO tab_group_class = project_tabs.BoardDetailTabs template_name = 'horizon/common/_detail.html' page_title = "{{ board.name|default:board.uuid }}" def get_context_data(self, **kwargs): context = super(DetailView, self).get_context_data(**kwargs) - # FROM - """ - instance = self.get_data() - if instance.image: - instance.image_url = reverse(self.image_url, - args=[instance.image['id']]) - instance.volume_url = self.volume_url - context["instance"] = instance - context["url"] = reverse(self.redirect_url) - context["actions"] = self._get_actions(instance) - """ - # TO board = self.get_data() context["board"] = board context["url"] = reverse(self.redirect_url) @@ -226,94 +455,38 @@ class DetailView(tabs.TabView): return context - # FROM - """ - def _get_actions(self, instance): - table = project_tables.InstancesTable(self.request) - return table.render_row_actions(instance) - """ - # TO def _get_actions(self, board): table = project_tables.BoardsTable(self.request) return table.render_row_actions(board) - # FROM - """ - @memoized.memoized_method - def get_data(self): - instance_id = self.kwargs['instance_id'] - - try: - instance = api.nova.server_get(self.request, instance_id) - except Exception: - redirect = reverse(self.redirect_url) - exceptions.handle(self.request, - _('Unable to retrieve details for ' - 'instance "%s".') % instance_id, - redirect=redirect) - # Not all exception types handled above will result in a redirect. - # Need to raise here just in case. - raise exceptions.Http302(redirect) - - choices = project_tables.STATUS_DISPLAY_CHOICES - instance.status_label = ( - filters.get_display_label(choices, instance.status)) - - try: - instance.volumes = api.nova.instance_volumes_list(self.request, - instance_id) - # Sort by device name - instance.volumes.sort(key=lambda vol: vol.device) - except Exception: - msg = _('Unable to retrieve volume list for instance ' - '"%(name)s" (%(id)s).') % {'name': instance.name, - 'id': instance_id} - exceptions.handle(self.request, msg, ignore=True) - - try: - instance.full_flavor = api.nova.flavor_get( - self.request, instance.flavor["id"]) - except Exception: - msg = _('Unable to retrieve flavor information for instance ' - '"%(name)s" (%(id)s).') % {'name': instance.name, - 'id': instance_id} - exceptions.handle(self.request, msg, ignore=True) - - try: - instance.security_groups = api.network.server_security_groups( - self.request, instance_id) - except Exception: - msg = _('Unable to retrieve security groups for instance ' - '"%(name)s" (%(id)s).') % {'name': instance.name, - 'id': instance_id} - exceptions.handle(self.request, msg, ignore=True) - - try: - api.network.servers_update_addresses(self.request, [instance]) - except Exception: - msg = _('Unable to retrieve IP addresses from Neutron for ' - 'instance "%(name)s" (%(id)s).') % {'name': instance.name, - 'id': instance_id} - exceptions.handle(self.request, msg, ignore=True) - - return instance - """ - - # TO @memoized.memoized_method def get_data(self): board_id = self.kwargs['board_id'] try: - board_services = [] - board_plugins = [] + board_ports = [] - board = iotronic.board_get(self.request, board_id, None) - board_services = iotronic.services_on_board(self.request, board_id, True) + board = api.iotronic.board_get(self.request, board_id, None) + + # FIX this problem with the new APIs + # (remove the "if" clause with a better approach) + # ################################################################# + ports = api.iotronic.port_list(self.request, board_id) + + for port in ports: + if port._info["board_uuid"] == board_id: + board_ports.append(port._info) + board._info.update(dict(ports=board_ports)) + # ################################################################# + + board_services = api.iotronic.services_on_board(self.request, + board_id, True) board._info.update(dict(services=board_services)) - board_plugins = iotronic.plugins_on_board(self.request, board_id) + board_plugins = api.iotronic.plugins_on_board(self.request, + board_id) board._info.update(dict(plugins=board_plugins)) + # LOG.debug("BOARD: %s\n\n%s", board, board._info) except Exception: @@ -322,14 +495,6 @@ class DetailView(tabs.TabView): exceptions.handle(self.request, msg, ignore=True) return board - # FROM - """ - def get_tabs(self, request, *args, **kwargs): - instance = self.get_data() - return self.tab_group_class(request, instance=instance, **kwargs) - """ - - # TO def get_tabs(self, request, *args, **kwargs): board = self.get_data() return self.tab_group_class(request, board=board, **kwargs) diff --git a/iotronic_ui/iot/dashboard.py b/iotronic_ui/iot/dashboard.py index 3660d9b..ed7ab1d 100644 --- a/iotronic_ui/iot/dashboard.py +++ b/iotronic_ui/iot/dashboard.py @@ -23,4 +23,5 @@ class Iot(horizon.Dashboard): # Specify the slug of the dashboard's default panel. default_panel = 'boards' + horizon.register(Iot) diff --git a/iotronic_ui/iot/plugins/forms.py b/iotronic_ui/iot/plugins/forms.py index 8ef14ba..5e9f3dd 100644 --- a/iotronic_ui/iot/plugins/forms.py +++ b/iotronic_ui/iot/plugins/forms.py @@ -68,18 +68,17 @@ class CreatePluginForm(forms.SelfHandlingForm): data["parameters"] = json.loads(data["parameters"]) try: - plugin = iotronic.plugin_create(request, data["name"], - data["public"], data["callable"], - data["code"], data["parameters"]) - LOG.debug("MELO API REQ: %s", request) + iotronic.plugin_create(request, data["name"], + data["public"], data["callable"], + data["code"], data["parameters"]) messages.success(request, _("Plugin created successfully.")) - return plugin + return True # except iot_exceptions.ClientException: except Exception: - # LOG.debug("MELO API REQ EXC: %s", request) - # LOG.debug("MELO API REQ (DICT): %s", exceptions.__dict__) + # LOG.debug("API REQ EXC: %s", request) + # LOG.debug("API REQ (DICT): %s", exceptions.__dict__) exceptions.handle(request, _('Unable to create plugin.')) @@ -97,7 +96,7 @@ class InjectPluginForm(forms.SelfHandlingForm): board_list = forms.MultipleChoiceField( label=_("Boards List"), widget=forms.SelectMultiple( - attrs={'class': 'switchable', 'data-slug': 'slug-inject-boards'}), + attrs={'class': 'switchable', 'data-slug': 'slug-inject-plugin'}), help_text=_("Select boards in this pool ") ) @@ -120,19 +119,18 @@ class InjectPluginForm(forms.SelfHandlingForm): if key == board: try: - plugin = None - plugin = iotronic.plugin_inject(request, key, + inject = iotronic.plugin_inject(request, key, data["uuid"], data["onboot"]) - # LOG.debug("MELO API: %s %s", plugin, request) - message_text = "Plugin injected successfully on " \ - "board " + str(value) + "." + # LOG.debug("API: %s %s", plugin, request) + message_text = inject messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: counter += 1 else: - return plugin + return True + except Exception: message_text = "Unable to inject plugin on board " \ + str(value) + "." @@ -191,20 +189,19 @@ class StartPluginForm(forms.SelfHandlingForm): if key == board: try: - plugin = None - plugin = iotronic.plugin_action(request, key, + action = iotronic.plugin_action(request, key, data["uuid"], "PluginStart", data["parameters"]) - # LOG.debug("MELO API: %s %s", plugin, request) - message_text = "Plugin started successfully on board "\ - + str(value) + "." + # LOG.debug("API: %s %s", plugin, request) + message_text = action messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: counter += 1 else: - return plugin + return True + except Exception: message_text = "Unable to start plugin on board " \ + str(value) + "." @@ -259,20 +256,19 @@ class StopPluginForm(forms.SelfHandlingForm): if key == board: try: - plugin = None - plugin = iotronic.plugin_action(request, key, + action = iotronic.plugin_action(request, key, data["uuid"], "PluginStop", data["delay"]) - # LOG.debug("MELO API: %s %s", plugin, request) - message_text = "Plugin stopped successfully on board "\ - + str(value) + "." + # LOG.debug("API: %s %s", plugin, request) + message_text = action messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: counter += 1 else: - return plugin + return True + except Exception: message_text = "Unable to stop plugin on board " \ + str(value) + "." @@ -330,20 +326,19 @@ class CallPluginForm(forms.SelfHandlingForm): if key == board: try: - plugin = None - plugin = iotronic.plugin_action(request, key, + action = iotronic.plugin_action(request, key, data["uuid"], "PluginCall", data["parameters"]) - # LOG.debug("MELO API: %s %s", plugin, request) - message_text = "Plugin called successfully on board " \ - + str(value) + "." + + message_text = action messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: - counter += 2 + counter += 1 else: - return plugin + return True + except Exception: message_text = "Unable to call plugin on board " \ + str(value) + "." @@ -387,10 +382,10 @@ class RemovePluginForm(forms.SelfHandlingForm): if key == board: try: - plugin = None - plugin = iotronic.plugin_remove(request, key, - data["uuid"]) - # LOG.debug("MELO API: %s %s", plugin, request) + iotronic.plugin_remove(request, + key, + data["uuid"]) + # LOG.debug("API: %s %s", plugin, request) message_text = "Plugin removed successfully from" \ + " board " + str(value) + "." messages.success(request, _(message_text)) @@ -398,7 +393,8 @@ class RemovePluginForm(forms.SelfHandlingForm): if counter != len(data["board_list"]) - 1: counter += 1 else: - return plugin + return True + except Exception: message_text = "Unable to remove plugin from board " \ + str(value) + "." @@ -411,34 +407,7 @@ class UpdatePluginForm(forms.SelfHandlingForm): uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput) owner = forms.CharField(label=_("Owner"), widget=forms.HiddenInput) - - """ name = forms.CharField(label=_("Plugin Name")) - public = forms.ChoiceField(label=_("Public")) - callable = forms.ChoiceField(label=_("Callable")) - code = forms.CharField(label=_("Code")) - """ - - name = forms.CharField(label=_("Plugin Name")) - - """ - public = forms.ChoiceField( - label=_("Public"), - choices =[('false', _('False')), ('true', _('True'))], - widget=forms.Select( - attrs={'class': 'switchable', 'data-slug': 'slug-public'}, - ) - ) - - - callable = forms.ChoiceField( - label=_("Callable"), - choices =[('false', _('False')), ('true', _('True'))], - widget=forms.Select( - attrs={'class': 'switchable', 'data-slug': 'slug-callable'}, - ) - ) - """ public = forms.BooleanField(label=_("Public"), required=False) callable = forms.BooleanField(label=_("Callable"), required=False) @@ -454,16 +423,16 @@ class UpdatePluginForm(forms.SelfHandlingForm): # Admin if policy.check((("iot", "iot:update_plugins"),), self.request): - # LOG.debug("MELO ADMIN") + # LOG.debug("ADMIN") pass # Admin_iot_project elif policy.check((("iot", "iot:update_project_plugins"),), self.request): - # LOG.debug("MELO IOT ADMIN") + # LOG.debug("IOT ADMIN") if self.request.user.id != kwargs["initial"]["owner"]: - # LOG.debug("MELO NO-edit IOT ADMIN") + # LOG.debug("NO-edit IOT ADMIN") self.fields["name"].widget.attrs = {'readonly': 'readonly'} self.fields["public"].widget.attrs = {'disabled': 'disabled'} self.fields["callable"].widget.attrs = {'disabled': 'disabled'} @@ -472,7 +441,7 @@ class UpdatePluginForm(forms.SelfHandlingForm): # Other users else: if self.request.user.id != kwargs["initial"]["owner"]: - # LOG.debug("MELO IMMUTABLE FIELDS") + # LOG.debug("IMMUTABLE FIELDS") self.fields["name"].widget.attrs = {'readonly': 'readonly'} self.fields["public"].widget.attrs = {'disabled': 'disabled'} self.fields["callable"].widget.attrs = {'disabled': 'disabled'} @@ -481,7 +450,6 @@ class UpdatePluginForm(forms.SelfHandlingForm): def handle(self, request, data): try: - # LOG.debug("MELO DATA: %s", data) data["code"] = cPickle.dumps(str(data["code"])) iotronic.plugin_update(request, data["uuid"], @@ -489,7 +457,10 @@ class UpdatePluginForm(forms.SelfHandlingForm): "public": data["public"], "callable": data["callable"], "code": data["code"]}) - messages.success(request, _("Plugin updated successfully.")) + + messages.success(request, _("Plugin " + str(data["name"]) + + " updated successfully.")) return True + except Exception: exceptions.handle(request, _('Unable to update plugin.')) diff --git a/iotronic_ui/iot/plugins/panel.py b/iotronic_ui/iot/plugins/panel.py index a099a3d..2625c5d 100644 --- a/iotronic_ui/iot/plugins/panel.py +++ b/iotronic_ui/iot/plugins/panel.py @@ -14,9 +14,14 @@ from django.utils.translation import ugettext_lazy as _ import horizon +from iotronic_ui.iot import dashboard + class Plugins(horizon.Panel): name = _("Plugins") slug = "plugins" # policy_rules = (("iot", "iot:list_all_plugins"), # ("iot", "iot:list_project_plugins")) + + +dashboard.Iot.register(Plugins) diff --git a/iotronic_ui/iot/plugins/tables.py b/iotronic_ui/iot/plugins/tables.py index 464ec46..362d7d6 100644 --- a/iotronic_ui/iot/plugins/tables.py +++ b/iotronic_ui/iot/plugins/tables.py @@ -28,7 +28,7 @@ class CreatePluginLink(tables.LinkAction): url = "horizon:iot:plugins:create" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:create_plugin"),) class EditPluginLink(tables.LinkAction): @@ -37,12 +37,12 @@ class EditPluginLink(tables.LinkAction): url = "horizon:iot:plugins:update" classes = ("ajax-modal",) icon = "pencil" - # policy_rules = (("iot", "iot:update_board"),) + # policy_rules = (("iot", "iot:update_plugin"),) """ def allowed(self, request, plugin): - # LOG.debug("MELO ALLOWED: %s %s %s", self, request, plugin) - # LOG.debug("MELO user: %s", request.user.id) + # LOG.debug("ALLOWED: %s %s %s", self, request, plugin) + # LOG.debug("user: %s", request.user.id) return True """ @@ -54,7 +54,7 @@ class InjectPluginLink(tables.LinkAction): url = "horizon:iot:plugins:inject" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:inject_plugin"),) class StartPluginLink(tables.LinkAction): @@ -63,7 +63,7 @@ class StartPluginLink(tables.LinkAction): url = "horizon:iot:plugins:start" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:start_plugin"),) class StopPluginLink(tables.LinkAction): @@ -72,7 +72,7 @@ class StopPluginLink(tables.LinkAction): url = "horizon:iot:plugins:stop" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:stop_plugin"),) class CallPluginLink(tables.LinkAction): @@ -81,7 +81,7 @@ class CallPluginLink(tables.LinkAction): url = "horizon:iot:plugins:call" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:call_plugin"),) class RemovePluginLink(tables.LinkAction): @@ -90,7 +90,7 @@ class RemovePluginLink(tables.LinkAction): url = "horizon:iot:plugins:remove" classes = ("ajax-modal",) icon = "plus" - # policy_rules = (("iot", "iot:create_board"),) + # policy_rules = (("iot", "iot:remove_plugin"),) class DeletePluginsAction(tables.DeleteAction): @@ -109,7 +109,7 @@ class DeletePluginsAction(tables.DeleteAction): u"Deleted Plugins", count ) - # policy_rules = (("iot", "iot:delete_board"),) + # policy_rules = (("iot", "iot:delete_plugin"),) """ def allowed(self, request, role): @@ -140,14 +140,14 @@ class PluginsTable(tables.DataTable): # Overriding get_object_id method because in IoT service the "id" is # identified by the field UUID def get_object_id(self, datum): - # LOG.debug("MELO datum %s", datum) + # LOG.debug("datum %s", datum) return datum.uuid # Overriding get_row_actions method because we need to discriminate # between Sync and Async plugins def get_row_actions(self, datum): actions = super(PluginsTable, self).get_row_actions(datum) - # LOG.debug("MELO ACTIONS: %s %s", actions[0].name, datum.name) + # LOG.debug("ACTIONS: %s %s", actions[0].name, datum.name) selected_row_actions = [] diff --git a/iotronic_ui/iot/plugins/urls.py b/iotronic_ui/iot/plugins/urls.py index 1894504..90a3d03 100644 --- a/iotronic_ui/iot/plugins/urls.py +++ b/iotronic_ui/iot/plugins/urls.py @@ -18,6 +18,8 @@ from iotronic_ui.iot.plugins import views urlpatterns = [ url(r'^$', views.IndexView.as_view(), name='index'), url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/update/$', views.UpdateView.as_view(), + name='update'), url(r'^(?P[^/]+)/inject/$', views.InjectView.as_view(), name='inject'), url(r'^(?P[^/]+)/start/$', views.StartView.as_view(), @@ -28,8 +30,6 @@ urlpatterns = [ name='call'), url(r'^(?P[^/]+)/remove/$', views.RemoveView.as_view(), name='remove'), - url(r'^(?P[^/]+)/update/$', views.UpdateView.as_view(), - name='update'), url(r'^(?P[^/]+)/detail/$', views.PluginDetailView.as_view(), name='detail'), ] diff --git a/iotronic_ui/iot/plugins/views.py b/iotronic_ui/iot/plugins/views.py index 1cb3f13..093acb8 100644 --- a/iotronic_ui/iot/plugins/views.py +++ b/iotronic_ui/iot/plugins/views.py @@ -67,13 +67,6 @@ class IndexView(tables.DataTableView): # Other users else: - # FROM - """ - msg = _("Insufficient privilege level to view - plugins information.") - messages.info(self.request, msg) - """ - # TO try: plugins = iotronic.plugin_list(self.request, None, None, with_public=True) diff --git a/iotronic_ui/iot/services/forms.py b/iotronic_ui/iot/services/forms.py index c8297c3..f3fa65f 100644 --- a/iotronic_ui/iot/services/forms.py +++ b/iotronic_ui/iot/services/forms.py @@ -41,17 +41,18 @@ class CreateServiceForm(forms.SelfHandlingForm): def handle(self, request, data): try: - # LOG.error("DATA: %s", data) - service = iotronic.service_create(request, data["name"], - data["port"], data["protocol"]) - messages.success(request, _("Service created successfully.")) + iotronic.service_create(request, data["name"], + data["port"], data["protocol"]) + + messages.success(request, _("Service " + str(data["name"]) + + " created successfully.")) + return True - return service except Exception: exceptions.handle(request, _('Unable to create service.')) -class UpdateBoardForm(forms.SelfHandlingForm): +class UpdateServiceForm(forms.SelfHandlingForm): uuid = forms.CharField(label=_("Service ID"), widget=forms.HiddenInput) name = forms.CharField(label=_("Service Name")) port = forms.IntegerField(label=_("Port")) @@ -65,23 +66,23 @@ class UpdateBoardForm(forms.SelfHandlingForm): def __init__(self, *args, **kwargs): - super(UpdateBoardForm, self).__init__(*args, **kwargs) + super(UpdateServiceForm, self).__init__(*args, **kwargs) # Admin if policy.check((("iot", "iot:update_services"),), self.request): - # LOG.debug("MELO ADMIN") + # LOG.debug("ADMIN") pass # Manager or Admin of the iot project elif (policy.check((("iot", "iot_manager"),), self.request) or policy.check((("iot", "iot_admin"),), self.request)): - # LOG.debug("MELO NO-edit IOT ADMIN") + # LOG.debug("NO-edit IOT ADMIN") pass # Other users else: if self.request.user.id != kwargs["initial"]["owner"]: - # LOG.debug("MELO IMMUTABLE FIELDS") + # LOG.debug("IMMUTABLE FIELDS") self.fields["name"].widget.attrs = {'readonly': 'readonly'} self.fields["port"].widget.attrs = {'readonly': 'readonly'} self.fields["protocol"].widget.attrs = {'readonly': 'readonly'} @@ -89,12 +90,13 @@ class UpdateBoardForm(forms.SelfHandlingForm): def handle(self, request, data): try: iotronic.service_update(request, data["uuid"], - {"name": data["name"], - "port": data["port"], - "protocol": data["protocol"]}) + {"name": data["name"], + "port": data["port"], + "protocol": data["protocol"]}) messages.success(request, _("Service updated successfully.")) return True + except Exception: exceptions.handle(request, _('Unable to update service.')) @@ -117,7 +119,9 @@ class ServiceActionForm(forms.SelfHandlingForm): action = forms.ChoiceField( label=_("Action"), - choices=[('ServiceEnable', _('Enable')), ('ServiceDisable', _('Disable')), ('ServiceRestore', _('Restore'))], + choices=[('ServiceEnable', _('Enable')), + ('ServiceDisable', _('Disable')), + ('ServiceRestore', _('Restore'))], widget=forms.Select( attrs={'class': 'switchable', 'data-slug': 'slug-action'}, ) @@ -128,10 +132,7 @@ class ServiceActionForm(forms.SelfHandlingForm): super(ServiceActionForm, self).__init__(*args, **kwargs) # input=kwargs.get('initial',{}) - boardslist_length = len(kwargs["initial"]["board_list"]) - self.fields["board_list"].choices = kwargs["initial"]["board_list"] - # self.fields["board_list"].max_length = boardslist_length def handle(self, request, data): @@ -142,60 +143,20 @@ class ServiceActionForm(forms.SelfHandlingForm): if key == board: try: - action = None action = iotronic.service_action(request, key, - data["uuid"], - data["action"]) - - message_text = "Action executed successfully on " \ - "board " + str(value) + "." + data["uuid"], + data["action"]) + message_text = action messages.success(request, _(message_text)) if counter != len(data["board_list"]) - 1: counter += 1 else: - return action + return True + except Exception: message_text = "Unable to execute action on board " \ + str(value) + "." exceptions.handle(request, _(message_text)) break - - -class RemoveServicesForm(forms.SelfHandlingForm): - - uuid = forms.CharField(label=_("Service ID"), widget=forms.HiddenInput) - - name = forms.CharField( - label=_('Service Name'), - widget=forms.TextInput(attrs={'readonly': 'readonly'}) - ) - port = forms.IntegerField( - label=_("Port"), - widget=forms.TextInput(attrs={'readonly': 'readonly'}) - ) - - protocol = forms.ChoiceField( - label=_("Protocol"), - choices=[('TCP', _('TCP')), ('UDP', _('UDP'))], - widget=forms.TextInput(attrs={'readonly': 'readonly'}) - ) - - def __init__(self, *args, **kwargs): - - super(RemoveServicesForm, self).__init__(*args, **kwargs) - # input=kwargs.get('initial',{}) - - - def handle(self, request, data): - - try: - message_text = "Service "+str(data["name"])+" deleted successfully." - - iotronic.service_delete(request, data["uuid"]) - messages.success(request, _(message_text)) - return True - except Exception: - message_text = "Unable to delete service "+str(data["name"])+"." - exceptions.handle(request, _(message_text)) diff --git a/iotronic_ui/iot/services/panel.py b/iotronic_ui/iot/services/panel.py index aa531a0..9f52117 100644 --- a/iotronic_ui/iot/services/panel.py +++ b/iotronic_ui/iot/services/panel.py @@ -15,6 +15,7 @@ from django.utils.translation import ugettext_lazy as _ import horizon # from openstack_dashboard.api import keystone +from iotronic_ui.iot import dashboard class Services(horizon.Panel): @@ -23,3 +24,5 @@ class Services(horizon.Panel): permissions = ('openstack.services.iot', ) # policy_rules = (("iot", "iot:list_all_services"),) + +dashboard.Iot.register(Services) diff --git a/iotronic_ui/iot/services/tables.py b/iotronic_ui/iot/services/tables.py index d4efc26..abe7cd9 100644 --- a/iotronic_ui/iot/services/tables.py +++ b/iotronic_ui/iot/services/tables.py @@ -22,7 +22,6 @@ from openstack_dashboard import api LOG = logging.getLogger(__name__) - class CreateServiceLink(tables.LinkAction): name = "create" verbose_name = _("Create Service") @@ -32,7 +31,7 @@ class CreateServiceLink(tables.LinkAction): # policy_rules = (("iot", "iot:create_service"),) -class EditBoardLink(tables.LinkAction): +class EditServiceLink(tables.LinkAction): name = "edit" verbose_name = _("Edit") url = "horizon:iot:services:update" @@ -41,17 +40,6 @@ class EditBoardLink(tables.LinkAction): # policy_rules = (("iot", "iot:update_service"),) -""" -class RemoveServicesLink(tables.LinkAction): - name = "remove" - verbose_name = _("Remove Service(s)") - url = "horizon:iot:services:remove" - classes = ("ajax-modal",) - icon = "plus" - # policy_rules = (("iot", "iot:delete_service"),) -""" - - class ActionServiceLink(tables.LinkAction): name = "action" verbose_name = _("Service Action") @@ -106,11 +94,7 @@ class ServicesTable(tables.DataTable): class Meta(object): name = "services" verbose_name = _("services") - row_actions = (EditBoardLink, ActionServiceLink, + row_actions = (EditServiceLink, ActionServiceLink, DeleteServicesAction) table_actions = (ServiceFilterAction, CreateServiceLink, - DeleteServicesAction) - - # row_actions = (EditBoardLink, RemovePluginsLink, DeleteBoardsAction) - # table_actions = (BoardFilterAction, CreateBoardLink, - # DeleteBoardsAction) + DeleteServicesAction) diff --git a/iotronic_ui/iot/services/urls.py b/iotronic_ui/iot/services/urls.py index b748942..fd61ed0 100644 --- a/iotronic_ui/iot/services/urls.py +++ b/iotronic_ui/iot/services/urls.py @@ -22,8 +22,6 @@ urlpatterns = [ name='update'), url(r'^(?P[^/]+)/action/$', views.ActionView.as_view(), name='action'), - url(r'^(?P[^/]+)/remove/$', - views.RemoveServicesView.as_view(), name='remove'), url(r'^(?P[^/]+)/detail/$', views.ServiceDetailView.as_view(), name='detail'), ] diff --git a/iotronic_ui/iot/services/views.py b/iotronic_ui/iot/services/views.py index b17a0f3..afc31cf 100644 --- a/iotronic_ui/iot/services/views.py +++ b/iotronic_ui/iot/services/views.py @@ -52,7 +52,8 @@ class IndexView(tables.DataTableView): _('Unable to retrieve services list.')) # Admin_iot_project - elif policy.check((("iot", "iot:list_project_services"),), self.request): + elif policy.check((("iot", "iot:list_project_services"),), + self.request): try: services = iotronic.service_list(self.request, None) @@ -87,7 +88,7 @@ class UpdateView(forms.ModalFormView): template_name = 'iot/services/update.html' modal_header = _("Update Service") form_id = "update_service_form" - form_class = project_forms.UpdateBoardForm + form_class = project_forms.UpdateServiceForm submit_label = _("Update Service") submit_url = "horizon:iot:services:update" success_url = reverse_lazy('horizon:iot:services:index') @@ -96,8 +97,9 @@ class UpdateView(forms.ModalFormView): @memoized.memoized_method def get_object(self): try: - return iotronic.service_get(self.request, self.kwargs['service_id'], - None) + return iotronic.service_get(self.request, + self.kwargs['service_id'], + None) except Exception: redirect = reverse("horizon:iot:services:index") exceptions.handle(self.request, @@ -133,8 +135,9 @@ class ActionView(forms.ModalFormView): @memoized.memoized_method def get_object(self): try: - return iotronic.service_get(self.request, self.kwargs['service_id'], - None) + return iotronic.service_get(self.request, + self.kwargs['service_id'], + None) except Exception: redirect = reverse("horizon:iot:services:index") exceptions.handle(self.request, @@ -163,43 +166,6 @@ class ActionView(forms.ModalFormView): 'board_list': board_list} -class RemoveServicesView(forms.ModalFormView): - template_name = 'iot/services/remove.html' - modal_header = _("Remove Service") - form_id = "remove_service_form" - form_class = project_forms.RemoveServicesForm - submit_label = _("Remove Service") - # submit_url = reverse_lazy("horizon:iot:boards:removeplugins") - submit_url = "horizon:iot:services:remove" - success_url = reverse_lazy('horizon:iot:services:index') - page_title = _("Remove Service") - - @memoized.memoized_method - def get_object(self): - try: - return iotronic.service_get(self.request, self.kwargs['service_id'], - None) - except Exception: - redirect = reverse("horizon:iot:services:index") - exceptions.handle(self.request, - _('Unable to get service information.'), - redirect=redirect) - - def get_context_data(self, **kwargs): - context = super(RemoveServicesView, self).get_context_data(**kwargs) - args = (self.get_object().uuid,) - context['submit_url'] = reverse(self.submit_url, args=args) - return context - - def get_initial(self): - service = self.get_object() - - return {'uuid': service.uuid, - 'name': service.name, - 'port': service.port, - 'protocol': service.protocol} - - class DetailView(tabs.TabView): tab_group_class = project_tabs.ServiceDetailTabs template_name = 'horizon/common/_detail.html' @@ -224,8 +190,8 @@ class DetailView(tabs.TabView): try: service = iotronic.service_get(self.request, service_id, None) except Exception: - msg = ('Unable to retrieve service %s information') % {'name': - service.name} + s = service.name + msg = ('Unable to retrieve service %s information') % {'name': s} exceptions.handle(self.request, msg, ignore=True) return service diff --git a/iotronic_ui/iot/static/iot/images/blue-circle.png b/iotronic_ui/iot/static/iot/images/blue-circle.png deleted file mode 100644 index 3e1f389d121aaadc4b6e238bd516fde82f4d3f83..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16224 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 diff --git a/iotronic_ui/iot/static/iot/images/marker-icon-green.png b/iotronic_ui/iot/static/iot/images/marker-icon-green.png deleted file mode 100644 index 56db5ea9ffa29c2bf85feb5a943e0ab0dc73a3c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1822 zcmV+(2jTdMP)P001cn1^@s6z>|W`00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-3$s303JDN(rN$z2CGR# zK~z}7otIr~Tt^kh|L4rydw1{pBaNN5*bk&piW6ETkl+w_sDwfbNIW1Rgb?t6LNhkW6b(AsrluY;4_y?gJ>oWsL9 z#v9wQ-MRW}rK>sTw{y;!Gj~W;v7E8((7RU~K|dgCgi%c-9((EG;}grVrQ1Q?|C6l) z!tyx+KBIzuCd#adybI2#YMB^U1Mq8AJ$(H0r%ta3@}5VC&F0NJ8T>@4GXZ)fK^I`z zf{381U=b8G0j^PzRdZ6+Z=sw%b@a0@`z0aUe!OWNgMVvHv@sqqDM~=_9_j({0tf&G z*fPihCNe-q@Rzhv_+o^;|HR8*I&)!B$hPMA>=^RLRfv|I1=aMJ-;W>K^}5o{O3R3p?bH|oMXaN-Lm zN6}jN%n0UZl}e?gy&i7L13_D%xkW^@;ClpxzZM{?Ju00Q%U*tQ(=`LY;QmdYX7CQb z!6&Zfp{i|yA)wnJx+6lAT10n5u-yg}(Y(2?=Ar9-V#sVC{Qjng0e}X7cwZ`xCOYq} zq`u1lz3yO~G?YaW=56QcX>Hk!{g}l(+=GSu1nxXF7wbmKne)nqrWl z*5;d$iU1{GWOQUf$jyIzZt|Z9iMCqK9D6bZnqsKnB$LQXQuB@sL4jFGuE_5$FgEc9 zetGtAD+D5Kzeg_r4zG^9iqPjFxwhGpA%wasIOyNZogY}!3Bkv>pJJ)WY}nK*C;<{% z!qHd$j%hoM^*!rRHYpSVFAN>Tp}!tP`K}U*t^(mkXzKu`!pN`qOp36d?t6M;pB228 zzOT$h-L;lIC+^WI3;W*~;j%}w*+g$gFQ&38EL+6)Byc?r&1u)R8QC)8N`%Hg8Uc`g zQdMkzX2Y?LzOKRIZTY+%9{?mJ3zITT!-B3ev_lX&Jc_2kH$2QO`Ld}+G#+bx^v1EF z$IoxK0Kks+WpCCG^V%p4jaE|Uj%MaSO#-tAbrRGmWJ+e|`CF>aB!uR8BV$M3o^6nh zq9ehDBDoqPjtmGgPtX7f4xw20=By&E5@V#4o&VwBP=Beu-ecF%at<}u*s>MVAOk$9aW(^g7 zYWT@ZwIv|{Ae!u#JmfsdNKFMwx~O7Kfk^;)XkJSi&oAU}LCEl)(+vo|A)_ucN$bE` zXkQQ!;>18kT?VDyukX6jTs8y%#AOG?g_>Rk5>6Mas0vk}R{?S1da_DhSj^X=kTZL) z0Nyv!2mqY< zgjs5&!@I diff --git a/iotronic_ui/iot/static/iot/images/marker-icon-red.png b/iotronic_ui/iot/static/iot/images/marker-icon-red.png deleted file mode 100644 index 3165a3132a2ec44323faffbbdd9dccef0b14dd9b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4518 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 Eg31n(2><{9 diff --git a/iotronic_ui/iot/static/iot/images/marker-icon.png b/iotronic_ui/iot/static/iot/images/marker-icon.png deleted file mode 100644 index e2e9f757f515ded172e6f72c3ce55bbe15579649..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1747 zcmV;^1}yoBP)P001cn1^@s6z>|W`000J>NklgF+#9zZY7a#;@J(5X0e&McXK2n7+jhR}<0i-1U5t`>D@ zJSJ*^swjdwq0keUf9!BETXZhVyjqS4&z|?2HdJnOU-HYF_xSyu=XsCkdtVv=(53>u zME@3F*5J;OHwJNJdWK(ivQ??rr&t7M)1yRas=d_yYH>g+p#{( zm+NoyW%|8bNfUkAMrabri(FY#Dqr5%zhZA&e^iALHXiJOFYA7Qt##L_a?_z6SW{&J zVeyp#G&snW>SO{*%d9CGVM}xic~V`MU$)*JU1Nbw2YX?ywi}|VZ4g;$g)p^+DoLHR zZ^Zr$S_=f^oU`+!4K^?NsU;H{;bhhex#H7(!s52U&FJ}OHQf-VvVd?Btj2MhF|zQI z%l~jBr~6T7^_WIHC1>8j&bv`c18g|Z3*l-jgeoml1{oh++Y75JI)RgU>LEY<%)C)X zI2rZ2kb>s^4cZuYeq3!A}DS`X~>Nd;+A$4e;ZwyD<1@2!8$ZKJ3w%!6)+sCALy+bKwyk zqKCS6qEGWmJ)97b-QXY|`<0lS5THkEtGgjE>ojOvuftfM&Ugd-rQg9C+|FdGM)HXs z(IxsA$&r(xg_j^4=hC;>s;1UF*Wp1I-2~sEF zZYgb?&`4bM1qjM*hR`yr3qJ(;L>Kj2XpBUy+)t((6z;bIr@-ihFNVAl4<#?{TWIaQ zIi>;YjXS_iIfNb?!N1t-!Y6vZvW5ZXE{%l7imzV9NvV4nf#G`Pcex;#@}?EINwsk7 z>UC=SlJC*b5a>-mgHP%q2+qF;cbNx ztCV@{s&fPsSt#;i@#G-m$as&$gLaG}ebOrtS5*22&glbhMH_fz8(~qVVN!VIVCKzg z1$U9^93(E%Ur9v_R*h#!t)Ce+#)+m(q^zCq%g&L-!E zC&et9WrT(49pl0S`?=DK7=`+;`CB!wP3ta97b)XdJ8K=@`DXOk0Q0};7zNT!`k4t@ z+)=9S)4p(jEGm5!qq)PDTmXiw3qDM19|fiylc@LtiQ=}Kfb#w&ckv^7tBj;cfwtY$q?IdliYlg zqh@4;IyW)OFPQPw4z|JsAEb7`+@yA@Q8SXQT#;upV`QNJ59Bg5m(jci3`0T4X-&^GU6)x7$Vi0XMWB(2hrdK^!hq0 zt!bDg$Hh)-9L62h`&{0PBf%7vM>69o`k4~kx^Wc)?lG!}=WgV2x-rq?HN#jMr^B0; p5zMeb2q5MEX5{g&Aa%N&e*pr!t%ZZ}>w*9P002ovPDHLkV1gpUS8xCT diff --git a/iotronic_ui/iot/static/iot/images/marker-shadow.png b/iotronic_ui/iot/static/iot/images/marker-shadow.png deleted file mode 100644 index d1e773c715a9b508ebea055c4bb4b0a2ad7f6e52..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 797 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
1p=5=wyS1U^c>c(`ZlR%-Gu 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 BBpUz# diff --git a/iotronic_ui/iot/static/iot/js/iot.js b/iotronic_ui/iot/static/iot/js/iot.js deleted file mode 100644 index 97e3b8d..0000000 --- a/iotronic_ui/iot/static/iot/js/iot.js +++ /dev/null @@ -1,122 +0,0 @@ -/* Additional JavaScript for iot. */ - -//alert('MELO'); - -//var image_url = 'https://ing-res-17.me.trigrid.it/iotronic/'; -var images_url = 'http://'+location.host+'/dashboard/static/iot/images/'; - -var markers = []; -markers = L.markerClusterGroup({ - spiderfyOnMaxZoom: false, - disableClusteringAtZoom: 17 -}); - - -var marker_red = L.icon({ - iconUrl: images_url+'marker-icon-red.png', - iconAnchor:[12.5, 41], - shadowUrl: images_url+'marker-shadow.png' -}); - - -var marker_green = L.icon({ - iconUrl: images_url+'marker-icon-green.png', - iconAnchor:[12.5, 41], - shadowUrl: images_url+'marker-shadow.png' -}); - - -var marker_blue = L.icon({ - iconUrl: images_url+'marker-icon.png', - iconAnchor:[12.5, 41], - shadowUrl: images_url+'marker-shadow.png' -}); - - -var labels = []; -var latitude = []; -var longitude = []; -var altitude = []; -var statuses = []; -var last_update = []; - - -function render_map(map_id, coordinates){ - var osmUrl='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - var osm = new L.TileLayer(osmUrl, {}); - - var lat = 38.20523; - var lon = 15.55972; - if(coordinates["coordinates"].length ==1){ - lat = coordinates["coordinates"][0].lat; - lon = coordinates["coordinates"][0].lon; - } - var map = L.map(map_id, {scrollWheelZoom:false, worldCopyJump: true}).setView([lat, lon], 12); - map.addLayer(osm); - - //Copyright - L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { - attribution: '© OpenStreetMap - by MDSLab' - }).addTo(map); - - //var marker = L.marker([lat, lon]); - //marker.setIcon(marker_red); - coord = coordinates["coordinates"]; - for(var i=0;i'; - if(statuses[sel] == "online") - img = ''; - if(statuses[sel] == "offline") - img = ''; - - var open_popup = '
'; - - var default_popup = '
'+img +' '+labels[sel]+'

' + - '
'+last_update[sel]+'

'+ - 'Latitude: '+latitude[sel]+ '
' + - 'Longitude: '+longitude[sel]+'
' + - 'Altitude: '+altitude[sel]+'

'; - - global_popup = open_popup + default_popup +"
"; - var popup = L.popup().setLatLng(e.latlng).setContent(global_popup).openOn(map); - }); - markers.addLayer(marker); - } - map.addLayer(markers); - //return map; -} - - - -function choose_marker(status){ - if(status=="online") return marker_green; - else if(status =="offline") return marker_red; - else return marker_blue; -} diff --git a/iotronic_ui/iot/static/iot/scss/iot.scss b/iotronic_ui/iot/static/iot/scss/iot.scss deleted file mode 100644 index e1bd25d..0000000 --- a/iotronic_ui/iot/static/iot/scss/iot.scss +++ /dev/null @@ -1,7 +0,0 @@ -/* Additional SCSS for {{ dash_name }}. */ - -/* -#mapdiv { - min-height: 300px; -} -*/