From 3c7c817ea1bd9723703959d830785d86090b70f6 Mon Sep 17 00:00:00 2001 From: Carmelo Romeo Date: Mon, 15 Oct 2018 11:00:21 +0200 Subject: [PATCH] Added fleet management Change-Id: I414fdbb331e2617f67bdae88f8daaa2915dc8926 --- iotronic_ui/enabled/_6040_iot_fleets_panel.py | 23 ++ iotronic_ui/iot/boards/forms.py | 30 ++- iotronic_ui/iot/boards/panel.py | 2 +- iotronic_ui/iot/boards/tables.py | 1 + .../templates/boards/_detail_overview.html | 51 ++-- iotronic_ui/iot/boards/views.py | 12 + iotronic_ui/iot/dashboard.py | 2 +- iotronic_ui/iot/fleets/__init__.py | 0 iotronic_ui/iot/fleets/forms.py | 151 ++++++++++++ iotronic_ui/iot/fleets/panel.py | 28 +++ iotronic_ui/iot/fleets/tables.py | 99 ++++++++ iotronic_ui/iot/fleets/tabs.py | 43 ++++ .../iot/fleets/templates/fleets/_action.html | 7 + .../iot/fleets/templates/fleets/_create.html | 8 + .../templates/fleets/_detail_overview.html | 25 ++ .../iot/fleets/templates/fleets/_update.html | 7 + .../iot/fleets/templates/fleets/action.html | 7 + .../iot/fleets/templates/fleets/create.html | 7 + .../iot/fleets/templates/fleets/index.html | 7 + .../iot/fleets/templates/fleets/update.html | 7 + iotronic_ui/iot/fleets/tests.py | 19 ++ iotronic_ui/iot/fleets/urls.py | 27 +++ iotronic_ui/iot/fleets/views.py | 220 ++++++++++++++++++ requirements.txt | 5 +- test-requirements.txt | 5 +- 25 files changed, 755 insertions(+), 38 deletions(-) create mode 100644 iotronic_ui/enabled/_6040_iot_fleets_panel.py create mode 100644 iotronic_ui/iot/fleets/__init__.py create mode 100644 iotronic_ui/iot/fleets/forms.py create mode 100644 iotronic_ui/iot/fleets/panel.py create mode 100644 iotronic_ui/iot/fleets/tables.py create mode 100644 iotronic_ui/iot/fleets/tabs.py create mode 100644 iotronic_ui/iot/fleets/templates/fleets/_action.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/_create.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/_detail_overview.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/_update.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/action.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/create.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/index.html create mode 100644 iotronic_ui/iot/fleets/templates/fleets/update.html create mode 100644 iotronic_ui/iot/fleets/tests.py create mode 100644 iotronic_ui/iot/fleets/urls.py create mode 100644 iotronic_ui/iot/fleets/views.py diff --git a/iotronic_ui/enabled/_6040_iot_fleets_panel.py b/iotronic_ui/enabled/_6040_iot_fleets_panel.py new file mode 100644 index 0000000..78cfaa5 --- /dev/null +++ b/iotronic_ui/enabled/_6040_iot_fleets_panel.py @@ -0,0 +1,23 @@ +# 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. + +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'fleets' +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'iot' +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = 'iot' +# If set, it will update the default panel of the PANEL_DASHBOARD. +DEFAULT_PANEL = '' + +# Python panel class of the PANEL to be added. +ADD_PANEL = 'iotronic_ui.iot.fleets.panel.Fleets' diff --git a/iotronic_ui/iot/boards/forms.py b/iotronic_ui/iot/boards/forms.py index e128360..908e032 100644 --- a/iotronic_ui/iot/boards/forms.py +++ b/iotronic_ui/iot/boards/forms.py @@ -81,6 +81,14 @@ class CreateBoardForm(forms.SelfHandlingForm): class UpdateBoardForm(forms.SelfHandlingForm): uuid = forms.CharField(label=_("Board ID"), widget=forms.HiddenInput) name = forms.CharField(label=_("Board Name")) + + fleet_list = forms.ChoiceField( + label=_("Fleets List"), + widget=forms.Select( + attrs={'class': 'switchable', 'data-slug': 'slug-fleet'}), + help_text=_("Select fleet in this pool ") + ) + mobile = forms.BooleanField(label=_("Mobile"), required=False) """ @@ -92,6 +100,7 @@ class UpdateBoardForm(forms.SelfHandlingForm): def __init__(self, *args, **kwargs): super(UpdateBoardForm, self).__init__(*args, **kwargs) + self.fields["fleet_list"].choices = kwargs["initial"]["fleet_list"] # LOG.debug("INITIAL: %s", kwargs["initial"]) @@ -117,6 +126,8 @@ class UpdateBoardForm(forms.SelfHandlingForm): # LOG.debug("IMMUTABLE FIELDS") self.fields["name"].widget.attrs = {'readonly': 'readonly'} self.fields["mobile"].widget.attrs = {'disabled': 'disabled'} + self.fields["fleet_list"].widget.attrs = {'disabled': + 'disabled'} """ self.fields["latitude"].widget.attrs = {'readonly': @@ -128,19 +139,20 @@ class UpdateBoardForm(forms.SelfHandlingForm): """ def handle(self, request, data): + try: - """ - data["location"] = [{"latitude": str(data["latitude"]), - "longitude": str(data["longitude"]), - "altitude": str(data["altitude"])}] - iotronic.board_update(request, data["uuid"], - {"name": data["name"], - "mobile": data["mobile"], - "location": data["location"]}) - """ + # data["location"] = [{"latitude": str(data["latitude"]), + # "longitude": str(data["longitude"]), + # "altitude": str(data["altitude"])}] + # iotronic.board_update(request, data["uuid"], + # {"name": data["name"], + # "mobile": data["mobile"], + # "location": data["location"]}) + iotronic.board_update(request, data["uuid"], {"name": data["name"], + "fleet": data["fleet_list"], "mobile": data["mobile"]}) messages.success(request, _("Board updated successfully.")) return True diff --git a/iotronic_ui/iot/boards/panel.py b/iotronic_ui/iot/boards/panel.py index fd23096..237da98 100644 --- a/iotronic_ui/iot/boards/panel.py +++ b/iotronic_ui/iot/boards/panel.py @@ -21,7 +21,7 @@ from iotronic_ui.iot import dashboard class Boards(horizon.Panel): name = _("Boards") slug = "boards" - permissions = ('openstack.services.iot', ) + # permissions = ('openstack.services.iot', ) # policy_rules = (("iot", "iot:list_all_boards"),) # TO BE REMOVED diff --git a/iotronic_ui/iot/boards/tables.py b/iotronic_ui/iot/boards/tables.py index afee155..7715774 100644 --- a/iotronic_ui/iot/boards/tables.py +++ b/iotronic_ui/iot/boards/tables.py @@ -175,6 +175,7 @@ class BoardsTable(tables.DataTable): type = tables.Column('type', verbose_name=_('Type')) # mobile = tables.Column('mobile', verbose_name=_('Mobile')) uuid = tables.Column('uuid', verbose_name=_('Board ID')) + fleet = tables.Column('fleet', verbose_name=_('Fleet ID')) # code = tables.Column('code', verbose_name=_('Code')) status = tables.Column('status', verbose_name=_('Status')) # location = tables.Column('location', verbose_name=_('Geo')) diff --git a/iotronic_ui/iot/boards/templates/boards/_detail_overview.html b/iotronic_ui/iot/boards/templates/boards/_detail_overview.html index f7e8e27..4628ae0 100644 --- a/iotronic_ui/iot/boards/templates/boards/_detail_overview.html +++ b/iotronic_ui/iot/boards/templates/boards/_detail_overview.html @@ -1,28 +1,33 @@ {% load i18n sizeformat %}
-
-
{% trans "Name" %}
-
{{ board.name }}
-
{% trans "Status" %}
-
{{ board.status }}
-
{% trans "Type" %}
-
{{ board.type }}
-
{% trans "ID" %}
-
{{ board.uuid }}
-
{% trans "Code" %}
-
{{ board.code }}
-
{% trans "Creation data" %}
-
{{ board.created_at }}
-
{% trans "Location" %}
-
Latitude: {{ coordinates.latitude }}
-
Longitude: {{ coordinates.longitude }}
-
Altitude: {{ coordinates.altitude }}
-
{% trans "Mobile" %}
-
{{ board.mobile }}
-
{% trans "Extra" %}
-
{{ board.extra }}
-
+ +

{% trans "Info" %}

+
+
+
{% trans "Name" %}
+
{{ board.name }}
+
{% trans "Status" %}
+
{{ board.status }}
+
{% trans "Type" %}
+
{{ board.type }}
+
{% trans "ID" %}
+
{{ board.uuid }}
+
{% trans "Code" %}
+
{{ board.code }}
+
{% trans "Creation data" %}
+
{{ board.created_at }}
+
{% trans "Location" %}
+
Latitude: {{ coordinates.latitude }}
+
Longitude: {{ coordinates.longitude }}
+
Altitude: {{ coordinates.altitude }}
+
{% trans "Mobile" %}
+
{{ board.mobile }}
+
{% trans "Extra" %}
+
{{ board.extra }}
+
{% trans "Fleet ID" %}
+
{{ board.fleet }}
+

{% trans "Ports" %}


@@ -61,7 +66,7 @@ {% else %}
--
{% endif %} - +
diff --git a/iotronic_ui/iot/boards/views.py b/iotronic_ui/iot/boards/views.py index de6a6bf..c87c33c 100644 --- a/iotronic_ui/iot/boards/views.py +++ b/iotronic_ui/iot/boards/views.py @@ -123,10 +123,19 @@ class UpdateView(forms.ModalFormView): board = self.get_object() location = board.location[0] + # Populate fleets + fleets = api.iotronic.fleet_list(self.request, None) + fleets.sort(key=lambda b: b.name) + + fleet_list = [] + for fleet in fleets: + fleet_list.append((fleet.uuid, _(fleet.name))) + return {'uuid': board.uuid, 'name': board.name, 'mobile': board.mobile, 'owner': board.owner, + 'fleet_list': fleet_list, 'latitude': location["latitude"], 'longitude': location["longitude"], 'altitude': location["altitude"]} @@ -461,6 +470,9 @@ class DetailView(tabs.TabView): @memoized.memoized_method def get_data(self): + + board = [] + board_id = self.kwargs['board_id'] try: diff --git a/iotronic_ui/iot/dashboard.py b/iotronic_ui/iot/dashboard.py index ed7ab1d..962c4ad 100644 --- a/iotronic_ui/iot/dashboard.py +++ b/iotronic_ui/iot/dashboard.py @@ -18,7 +18,7 @@ import horizon class Iot(horizon.Dashboard): name = _("IoT") slug = "iot" - panels = ('boards', 'plugins', 'services') # Add your panels here. + panels = ('boards', 'plugins', 'services', 'fleets') # Add your panels here. # Specify the slug of the dashboard's default panel. default_panel = 'boards' diff --git a/iotronic_ui/iot/fleets/__init__.py b/iotronic_ui/iot/fleets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/iotronic_ui/iot/fleets/forms.py b/iotronic_ui/iot/fleets/forms.py new file mode 100644 index 0000000..435553e --- /dev/null +++ b/iotronic_ui/iot/fleets/forms.py @@ -0,0 +1,151 @@ +# 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. + +import logging + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard.api import iotronic +from openstack_dashboard import policy + +LOG = logging.getLogger(__name__) + + +class CreateFleetForm(forms.SelfHandlingForm): + name = forms.CharField(label=_("Fleet Name")) + + description = forms.CharField( + label=_("Description"), + widget=forms.Textarea( + attrs={'class': 'switchable', 'data-slug': 'slug-description'}) + ) + + def handle(self, request, data): + try: + iotronic.fleet_create(request, data["name"], + data["description"]) + + messages.success(request, _("Fleet " + str(data["name"]) + + " created successfully.")) + return True + + except Exception: + exceptions.handle(request, _('Unable to create fleet.')) + + +class UpdateFleetForm(forms.SelfHandlingForm): + uuid = forms.CharField(label=_("Fleet ID"), widget=forms.HiddenInput) + name = forms.CharField(label=_("Fleet Name")) + description = forms.CharField( + label=_("Description"), + widget=forms.Textarea( + attrs={'class': 'switchable', 'data-slug': 'slug-description'}) + ) + + def __init__(self, *args, **kwargs): + + super(UpdateFleetForm, self).__init__(*args, **kwargs) + + # Admin + if policy.check((("iot", "iot:update_fleets"),), self.request): + # 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("NO-edit IOT ADMIN") + pass + + # Other users + else: + if self.request.user.id != kwargs["initial"]["owner"]: + # LOG.debug("IMMUTABLE FIELDS") + self.fields["name"].widget.attrs = {'readonly': 'readonly'} + self.fields["description"].widget.attrs = {'readonly': 'readonly'} + + def handle(self, request, data): + try: + iotronic.fleet_update(request, data["uuid"], + {"name": data["name"], + "description": data["description"]}) + + messages.success(request, _("Fleet updated successfully.")) + return True + + except Exception: + exceptions.handle(request, _('Unable to update fleet.')) + + +class FleetActionForm(forms.SelfHandlingForm): + + uuid = forms.CharField(label=_("Plugin ID"), widget=forms.HiddenInput) + + name = forms.CharField( + label=_('Fleet Name'), + widget=forms.TextInput(attrs={'readonly': 'readonly'}) + ) + + board_list = forms.MultipleChoiceField( + label=_("Boards List"), + widget=forms.SelectMultiple( + attrs={'class': 'switchable', 'data-slug': 'slug-select-boards'}), + help_text=_("Select boards in this pool") + ) + + action = forms.ChoiceField( + label=_("Action"), + choices=[('FleetEnable', _('Enable')), + ('FleetDisable', _('Disable')), + ('FleetRestore', _('Restore'))], + widget=forms.Select( + attrs={'class': 'switchable', 'data-slug': 'slug-action'}, + ) + ) + + def __init__(self, *args, **kwargs): + + super(FleetActionForm, self).__init__(*args, **kwargs) + # input=kwargs.get('initial',{}) + + self.fields["board_list"].choices = kwargs["initial"]["board_list"] + + def handle(self, request, data): + + counter = 0 + + for board in data["board_list"]: + for key, value in self.fields["board_list"].choices: + if key == board: + + try: + action = iotronic.fleet_action(request, key, + data["uuid"], + data["action"]) + message_text = action + messages.success(request, _(message_text)) + + if counter != len(data["board_list"]) - 1: + counter += 1 + else: + return True + + except Exception: + message_text = "Unable to execute action on board " \ + + str(value) + "." + exceptions.handle(request, _(message_text)) + + break diff --git a/iotronic_ui/iot/fleets/panel.py b/iotronic_ui/iot/fleets/panel.py new file mode 100644 index 0000000..a19fb0f --- /dev/null +++ b/iotronic_ui/iot/fleets/panel.py @@ -0,0 +1,28 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.utils.translation import ugettext_lazy as _ + +import horizon + +# from openstack_dashboard.api import keystone +from iotronic_ui.iot import dashboard + + +class Fleets(horizon.Panel): + name = _("Fleets") + slug = "fleets" + # permissions = ('openstack.fleets.iot', ) + # policy_rules = (("iot", "iot:list_all_fleets"),) + + +dashboard.Iot.register(Fleets) diff --git a/iotronic_ui/iot/fleets/tables.py b/iotronic_ui/iot/fleets/tables.py new file mode 100644 index 0000000..744e068 --- /dev/null +++ b/iotronic_ui/iot/fleets/tables.py @@ -0,0 +1,99 @@ +# 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. + +import logging + +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy + +from horizon import tables + +from openstack_dashboard import api + +LOG = logging.getLogger(__name__) + + +class CreateFleetLink(tables.LinkAction): + name = "create" + verbose_name = _("Create Fleet") + url = "horizon:iot:fleets:create" + classes = ("ajax-modal",) + icon = "plus" + # policy_rules = (("iot", "iot:create_fleet"),) + + +class EditFleetLink(tables.LinkAction): + name = "edit" + verbose_name = _("Edit") + url = "horizon:iot:fleets:update" + classes = ("ajax-modal",) + icon = "pencil" + # policy_rules = (("iot", "iot:update_fleet"),) + + +class ActionFleetLink(tables.LinkAction): + name = "action" + verbose_name = _("Fleet Action") + url = "horizon:iot:fleets:action" + classes = ("ajax-modal",) + # icon = "plus" + # policy_rules = (("iot", "iot:fleet_action"),) + + +class DeleteFleetsAction(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Fleet", + u"Delete Fleets", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Fleet", + u"Deleted Fleets", + count + ) + # policy_rules = (("iot", "iot:delete_fleet"),) + + def delete(self, request, fleet_id): + api.iotronic.fleet_delete(request, fleet_id) + + +class FleetFilterAction(tables.FilterAction): + + def filter(self, table, fleets, filter_string): + # Naive case-insensitive search. + q = filter_string.lower() + return [fleet for fleet in fleets + if q in fleet.name.lower()] + + +class FleetsTable(tables.DataTable): + name = tables.WrappingColumn('name', link="horizon:iot:fleets:detail", + verbose_name=_('Fleet Name')) + description = tables.Column('description', verbose_name=_('Description')) + + # Overriding get_object_id method because in IoT fleet the "id" is + # identified by the field UUID + def get_object_id(self, datum): + return datum.uuid + + class Meta(object): + name = "fleets" + verbose_name = _("fleets") + row_actions = (EditFleetLink, ActionFleetLink, + DeleteFleetsAction) + table_actions = (FleetFilterAction, CreateFleetLink, + DeleteFleetsAction) diff --git a/iotronic_ui/iot/fleets/tabs.py b/iotronic_ui/iot/fleets/tabs.py new file mode 100644 index 0000000..c0d8ddf --- /dev/null +++ b/iotronic_ui/iot/fleets/tabs.py @@ -0,0 +1,43 @@ +# 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. + +import logging + +# from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ + +from horizon import tabs + +LOG = logging.getLogger(__name__) + + +class OverviewTab(tabs.Tab): + name = _("Overview") + slug = "overview" + template_name = ("iot/fleets/_detail_overview.html") + + def get_context_data(self, request): + # coordinates = self.tab_group.kwargs['board'].__dict__["location"][0] + # LOG.debug('IOT INFO: %s', coordinates) + + boards = self.tab_group.kwargs['fleet']._info['boards'] + + return {"fleet": self.tab_group.kwargs['fleet'], + "boards": boards, + "is_superuser": request.user.is_superuser} + + +class FleetDetailTabs(tabs.TabGroup): + slug = "fleet_details" + # tabs = (OverviewTab, LogTab, ConsoleTab, AuditTab) + tabs = (OverviewTab,) + sticky = True diff --git a/iotronic_ui/iot/fleets/templates/fleets/_action.html b/iotronic_ui/iot/fleets/templates/fleets/_action.html new file mode 100644 index 0000000..37fb87d --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/_action.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Execute action on board(s)." %}

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

{% trans "Description:" %}

+

{% trans "Add a new fleet." %}

+{% endblock %} + diff --git a/iotronic_ui/iot/fleets/templates/fleets/_detail_overview.html b/iotronic_ui/iot/fleets/templates/fleets/_detail_overview.html new file mode 100644 index 0000000..a9a4a1d --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/_detail_overview.html @@ -0,0 +1,25 @@ +{% load i18n sizeformat %} + +
+
+
{% trans "Name" %}
+
{{ fleet.name }}
+
{% trans "ID" %}
+
{{ fleet.uuid }}
+
{% trans "Description" %}
+
{{ fleet.description }}
+
+ +

{% trans "Boards" %}

+
+
+ {% if boards %} + {% for board in boards %} +
{{ board.name }}
+
{{ board.uuid }}
+ {% endfor %} + {% else %} +
--
+ {% endif %} +
+
diff --git a/iotronic_ui/iot/fleets/templates/fleets/_update.html b/iotronic_ui/iot/fleets/templates/fleets/_update.html new file mode 100644 index 0000000..5ba06c5 --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/_update.html @@ -0,0 +1,7 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} + +{% block modal-body-right %} +

{% trans "Description:" %}

+

{% trans "Edit the fleet's details." %}

+{% endblock %} diff --git a/iotronic_ui/iot/fleets/templates/fleets/action.html b/iotronic_ui/iot/fleets/templates/fleets/action.html new file mode 100644 index 0000000..130a9a5 --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/action.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Execute Action" %}{% endblock %} + +{% block main %} + {% include 'iot/fleets/_action.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/fleets/templates/fleets/create.html b/iotronic_ui/iot/fleets/templates/fleets/create.html new file mode 100644 index 0000000..645cb9f --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/create.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Insert Fleet" %}{% endblock %} + +{% block main %} + {% include 'iot/fleets/_create.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/fleets/templates/fleets/index.html b/iotronic_ui/iot/fleets/templates/fleets/index.html new file mode 100644 index 0000000..f37897b --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/index.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Fleets" %}{% endblock %} + +{% block main %} + {{ table.render }} +{% endblock %} diff --git a/iotronic_ui/iot/fleets/templates/fleets/update.html b/iotronic_ui/iot/fleets/templates/fleets/update.html new file mode 100644 index 0000000..297769f --- /dev/null +++ b/iotronic_ui/iot/fleets/templates/fleets/update.html @@ -0,0 +1,7 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Fleet" %}{% endblock %} + +{% block main %} + {% include 'iot/fleets/_update.html' %} +{% endblock %} diff --git a/iotronic_ui/iot/fleets/tests.py b/iotronic_ui/iot/fleets/tests.py new file mode 100644 index 0000000..d1a529b --- /dev/null +++ b/iotronic_ui/iot/fleets/tests.py @@ -0,0 +1,19 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from horizon.test import helpers as test + + +class FleetsTests(test.TestCase): + # Unit tests for boards. + def test_me(self): + self.assertTrue(1 + 1 == 2) diff --git a/iotronic_ui/iot/fleets/urls.py b/iotronic_ui/iot/fleets/urls.py new file mode 100644 index 0000000..17d40f2 --- /dev/null +++ b/iotronic_ui/iot/fleets/urls.py @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.conf.urls import url + +from iotronic_ui.iot.fleets 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[^/]+)/action/$', views.ActionView.as_view(), + name='action'), + url(r'^(?P[^/]+)/detail/$', views.FleetDetailView.as_view(), + name='detail'), +] diff --git a/iotronic_ui/iot/fleets/views.py b/iotronic_ui/iot/fleets/views.py new file mode 100644 index 0000000..06d87b5 --- /dev/null +++ b/iotronic_ui/iot/fleets/views.py @@ -0,0 +1,220 @@ +# 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. + +import logging + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +# from horizon import messages +from horizon import tables +from horizon import tabs +from horizon.utils import memoized + +from openstack_dashboard.api import iotronic +from openstack_dashboard import policy + +from iotronic_ui.iot.fleets import forms as project_forms +from iotronic_ui.iot.fleets import tables as project_tables +from iotronic_ui.iot.fleets import tabs as project_tabs + + +LOG = logging.getLogger(__name__) + + +class IndexView(tables.DataTableView): + table_class = project_tables.FleetsTable + template_name = 'iot/fleets/index.html' + page_title = _("Fleets") + + def get_data(self): + fleets = [] + + # Admin + if policy.check((("iot", "iot:list_all_fleets"),), self.request): + try: + fleets = iotronic.fleet_list(self.request, None) + + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve fleets list.')) + + # Admin_iot_project + elif policy.check((("iot", "iot:list_project_fleets"),), + self.request): + try: + fleets = iotronic.fleet_list(self.request, None) + + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve user fleets list.')) + + # Other users + else: + try: + fleets = iotronic.fleet_list(self.request, None) + + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve user fleets list.')) + + return fleets + + +class CreateView(forms.ModalFormView): + template_name = 'iot/fleets/create.html' + modal_header = _("Create Fleet") + form_id = "create_fleet_form" + form_class = project_forms.CreateFleetForm + submit_label = _("Create Fleet") + submit_url = reverse_lazy("horizon:iot:fleets:create") + success_url = reverse_lazy('horizon:iot:fleets:index') + page_title = _("Create Fleet") + + +class UpdateView(forms.ModalFormView): + template_name = 'iot/fleets/update.html' + modal_header = _("Update Fleet") + form_id = "update_fleet_form" + form_class = project_forms.UpdateFleetForm + submit_label = _("Update Fleet") + submit_url = "horizon:iot:fleets:update" + success_url = reverse_lazy('horizon:iot:fleets:index') + page_title = _("Update Fleet") + + @memoized.memoized_method + def get_object(self): + try: + return iotronic.fleet_get(self.request, + self.kwargs['fleet_id'], + None) + except Exception: + redirect = reverse("horizon:iot:fleets:index") + exceptions.handle(self.request, + _('Unable to get fleet information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(UpdateView, 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): + fleet = self.get_object() + + return {'uuid': fleet.uuid, + 'name': fleet.name, + 'description': fleet.description} + + +class ActionView(forms.ModalFormView): + template_name = 'iot/fleets/action.html' + modal_header = _("Fleet Action") + form_id = "fleet_action_form" + form_class = project_forms.FleetActionForm + submit_label = _("Fleet Action") + # submit_url = reverse_lazy("horizon:iot:fleets:action") + submit_url = "horizon:iot:fleets:action" + success_url = reverse_lazy('horizon:iot:fleets:index') + page_title = _("Fleet Action") + + @memoized.memoized_method + def get_object(self): + try: + return iotronic.fleet_get(self.request, + self.kwargs['fleet_id'], + None) + except Exception: + redirect = reverse("horizon:iot:fleets:index") + exceptions.handle(self.request, + _('Unable to get fleet information.'), + redirect=redirect) + + def get_context_data(self, **kwargs): + context = super(ActionView, 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): + fleet = self.get_object() + + # Populate boards + boards = iotronic.board_list(self.request, "online", None, None) + boards.sort(key=lambda b: b.name) + + board_list = [] + for board in boards: + board_list.append((board.uuid, _(board.name))) + + return {'uuid': fleet.uuid, + 'name': fleet.name, + 'board_list': board_list} + + +class DetailView(tabs.TabView): + tab_group_class = project_tabs.FleetDetailTabs + template_name = 'horizon/common/_detail.html' + page_title = "{{ fleet.name|default:fleet.uuid }}" + + def get_context_data(self, **kwargs): + context = super(DetailView, self).get_context_data(**kwargs) + fleet = self.get_data() + context["fleet"] = fleet + context["url"] = reverse(self.redirect_url) + context["actions"] = self._get_actions(fleet) + + return context + + def _get_actions(self, fleet): + table = project_tables.FleetsTable(self.request) + return table.render_row_actions(fleet) + + @memoized.memoized_method + def get_data(self): + fleet = [] + fleet_boards = [] + + fleet_id = self.kwargs['fleet_id'] + try: + fleet = iotronic.fleet_get(self.request, fleet_id, None) + boards = iotronic.fleet_get_boards(self.request, fleet_id) + + LOG.debug('XXXX: %s', boards) + + for board in boards: + fleet_boards.append(board._info) + + fleet._info.update(dict(boards=fleet_boards)) + # LOG.debug('FLEET COMPLETE: %s', fleet) + + except Exception: + s = fleet.name + msg = ('Unable to retrieve fleet %s information') % {'name': s} + exceptions.handle(self.request, msg, ignore=True) + return fleet + + def get_tabs(self, request, *args, **kwargs): + fleet = self.get_data() + return self.tab_group_class(request, fleet=fleet, **kwargs) + + +class FleetDetailView(DetailView): + redirect_url = 'horizon:iot:fleets:index' + + def _get_actions(self, fleet): + table = project_tables.FleetsTable(self.request) + return table.render_row_actions(fleet) diff --git a/requirements.txt b/requirements.txt index 5f85fca..a4048db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,8 @@ # PBR should always appear first pbr>=2.0.0,!=2.1.0 # Apache-2.0 Babel>=2.3.4,!=2.4.0 # BSD -Django>=1.8,<2.0 # BSD -django-babel>=0.5.1 # BSD +Django<2,>=1.11;python_version<'3.0' # BSD +Django<2.1,>=1.11;python_version>='3.0' # BSD +django-babel>=0.6.2 # BSD django-compressor>=2.0 # MIT django-pyscss>=2.0.2 # BSD License (2 clause) diff --git a/test-requirements.txt b/test-requirements.txt index 5f85fca..a4048db 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -9,7 +9,8 @@ # PBR should always appear first pbr>=2.0.0,!=2.1.0 # Apache-2.0 Babel>=2.3.4,!=2.4.0 # BSD -Django>=1.8,<2.0 # BSD -django-babel>=0.5.1 # BSD +Django<2,>=1.11;python_version<'3.0' # BSD +Django<2.1,>=1.11;python_version>='3.0' # BSD +django-babel>=0.6.2 # BSD django-compressor>=2.0 # MIT django-pyscss>=2.0.2 # BSD License (2 clause)