diff --git a/senlin_dashboard/api/senlin.py b/senlin_dashboard/api/senlin.py index f70f442e..b965f417 100644 --- a/senlin_dashboard/api/senlin.py +++ b/senlin_dashboard/api/senlin.py @@ -45,8 +45,8 @@ class Node(base.APIResourceWrapper): class Event(base.APIResourceWrapper): - _attrs = ['id', 'obj_id', 'timestamp', 'status', 'status_reason', - 'action'] + _attrs = ['id', 'obj_id', 'obj_name', 'timestamp', 'status', + 'status_reason', 'action'] @memoized.memoized diff --git a/senlin_dashboard/cluster/nodes/event_tables.py b/senlin_dashboard/cluster/nodes/event_tables.py new file mode 100644 index 00000000..afe7e968 --- /dev/null +++ b/senlin_dashboard/cluster/nodes/event_tables.py @@ -0,0 +1,62 @@ +# 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 pgettext_lazy +from django.utils.translation import ugettext_lazy as _ + +from horizon import tables +from horizon.utils import filters + + +class EventsTable(tables.DataTable): + STATUS_CHOICES = ( + ("INIT", None), + ("ACTIVE", True), + ("ERROR", False), + ("DELETED", False), + ("WARNING", None), + ("CREATING", None), + ("UPDATING", None), + ("DELETING", None), + ) + + STATUS_DISPLAY_CHOICES = ( + ("INIT", pgettext_lazy("Current status of the event", u"INIT")), + ("ACTIVE", pgettext_lazy("Current status of the event", u"ACTIVE")), + ("ERROR", pgettext_lazy("Current status of the event", u"ERROR")), + ("DELETED", pgettext_lazy("Current status of the event", u"DELETED")), + ("WARNING", pgettext_lazy("Current status of the event", u"WARNING")), + ("CREATING", + pgettext_lazy("Current status of the event", u"CREATING")), + ("UPDATING", + pgettext_lazy("Current status of the event", u"UPDATING")), + ("DELETING", + pgettext_lazy("Current status of the event", u"DELETING")), + ) + + obj_id = tables.Column("obj_id", verbose_name=_("Object ID")) + obj_name = tables.Column("obj_name", verbose_name=_("Object Name")) + status = tables.Column("status", + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + status_reason = tables.Column("status_reason", + verbose_name=_("Status Reason")) + action = tables.Column("action", verbose_name=_("Action")) + timestamp = tables.Column("timestamp", + verbose_name=_("Timestamp"), + filters=(filters.parse_isotime,)) + + class Meta(object): + name = "event" + verbose_name = _("Event") diff --git a/senlin_dashboard/cluster/nodes/tabs.py b/senlin_dashboard/cluster/nodes/tabs.py index 11daf7fa..ce653068 100644 --- a/senlin_dashboard/cluster/nodes/tabs.py +++ b/senlin_dashboard/cluster/nodes/tabs.py @@ -12,8 +12,12 @@ from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions from horizon import tabs +from senlin_dashboard.api import senlin +from senlin_dashboard.cluster.nodes import event_tables + class OverviewTab(tabs.Tab): name = _("Overview") @@ -24,6 +28,25 @@ class OverviewTab(tabs.Tab): return {"node": self.tab_group.kwargs['node']} +class EventTab(tabs.TableTab): + name = _("Event") + slug = "event" + table_classes = (event_tables.EventsTable,) + template_name = "cluster/nodes/_detail_event.html" + preload = False + + def get_event_data(self): + node_id = self.tab_group.kwargs['node_id'] + try: + params = {"obj_id": node_id} + events = senlin.event_list(self.request, params) + except Exception: + events = [] + exceptions.handle(self.request, + _('Unable to retrieve node event list.')) + return sorted(events, reverse=True, key=lambda y: y.timestamp) + + class NodeDetailTabs(tabs.TabGroup): slug = "node_details" - tabs = (OverviewTab,) + tabs = (OverviewTab, EventTab) diff --git a/senlin_dashboard/cluster/nodes/templates/nodes/_detail_event.html b/senlin_dashboard/cluster/nodes/templates/nodes/_detail_event.html new file mode 100644 index 00000000..46c89cfc --- /dev/null +++ b/senlin_dashboard/cluster/nodes/templates/nodes/_detail_event.html @@ -0,0 +1,5 @@ +
+
+ {{ table.render }} +
+
diff --git a/senlin_dashboard/cluster/nodes/tests.py b/senlin_dashboard/cluster/nodes/tests.py index ed9390cf..73161412 100644 --- a/senlin_dashboard/cluster/nodes/tests.py +++ b/senlin_dashboard/cluster/nodes/tests.py @@ -22,7 +22,8 @@ from senlin_dashboard.test import helpers as test NODE_INDEX_URL = reverse('horizon:cluster:nodes:index') NODE_CREATE_URL = reverse('horizon:cluster:nodes:create') -NODE_DETAIL_URL = reverse('horizon:cluster:nodes:detail', args=[u'1']) +NODE_DETAIL_URL = reverse('horizon:cluster:nodes:detail', + args=[u'123456']) class NodesTest(test.TestCase): @@ -94,9 +95,25 @@ class NodesTest(test.TestCase): def test_node_detail(self): node = self.nodes.list()[0] api.senlin.node_get( - IsA(http.HttpRequest), u'1').AndReturn(node) + IsA(http.HttpRequest), u'123456').AndReturn(node) self.mox.ReplayAll() res = self.client.get(NODE_DETAIL_URL) self.assertTemplateUsed(res, 'horizon/common/_detail.html') self.assertContains(res, 'test-node') + + @test.create_stubs({api.senlin: ('event_list', + 'node_get')}) + def test_node_event(self): + events = self.events.list() + node = self.nodes.list()[0] + api.senlin.node_get( + IsA(http.HttpRequest), u'123456').AndReturn(node) + api.senlin.event_list( + IsA(http.HttpRequest), + params={'obj_id': u'123456'}).AndReturn(events) + self.mox.ReplayAll() + + res = self.client.get(NODE_DETAIL_URL + '?tab=node_details__event') + self.assertTemplateUsed(res, 'cluster/nodes/_detail_event.html') + self.assertContains(res, '123456') diff --git a/senlin_dashboard/test/test_data/senlin_data.py b/senlin_dashboard/test/test_data/senlin_data.py index 456fe647..56917825 100644 --- a/senlin_dashboard/test/test_data/senlin_data.py +++ b/senlin_dashboard/test/test_data/senlin_data.py @@ -63,6 +63,7 @@ def data(TEST): # Nodes TEST.nodes = test_data_utils.TestDataContainer() node_1 = mock.Mock() + node_1.id = "123456" node_1.name = "test-node" TEST.nodes.add(node_1)