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)