diff --git a/tailbone/templates/tempmon/probes/view.mako b/tailbone/templates/tempmon/probes/view.mako index 386cc4db..8acdbfbe 100644 --- a/tailbone/templates/tempmon/probes/view.mako +++ b/tailbone/templates/tempmon/probes/view.mako @@ -44,8 +44,10 @@ <%def name="render_main_fields(form)"> ${form.render_field_readonly('client')} ${form.render_field_readonly('config_key')} + ${form.render_field_readonly('appliance')} ${form.render_field_readonly('appliance_type')} ${form.render_field_readonly('description')} + ${form.render_field_readonly('location')} ${form.render_field_readonly('device_path')} ${form.render_field_readonly('notes')} ${form.render_field_readonly('enabled')} diff --git a/tailbone/views/tempmon/__init__.py b/tailbone/views/tempmon/__init__.py index 5f26a065..61ce6bf0 100644 --- a/tailbone/views/tempmon/__init__.py +++ b/tailbone/views/tempmon/__init__.py @@ -30,6 +30,7 @@ from .core import MasterView def includeme(config): + config.include('tailbone.views.tempmon.appliances') config.include('tailbone.views.tempmon.clients') config.include('tailbone.views.tempmon.probes') config.include('tailbone.views.tempmon.readings') diff --git a/tailbone/views/tempmon/appliances.py b/tailbone/views/tempmon/appliances.py new file mode 100644 index 00000000..1da74dd0 --- /dev/null +++ b/tailbone/views/tempmon/appliances.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2018 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +Views for tempmon appliances +""" + +from __future__ import unicode_literals, absolute_import + +from rattail_tempmon.db import model as tempmon + +from webhelpers2.html import HTML, tags + +from tailbone.views.tempmon import MasterView + + +class TempmonApplianceView(MasterView): + """ + Master view for tempmon appliances. + """ + model_class = tempmon.Appliance + model_title = "TempMon Appliance" + model_title_plural = "TempMon Appliances" + route_prefix = 'tempmon.appliances' + url_prefix = '/tempmon/appliances' + + grid_columns = [ + 'name', + 'location', + ] + + form_fields = [ + 'name', + 'location', + 'clients', + 'probes', + ] + + def configure_grid(self, g): + super(TempmonApplianceView, self).configure_grid(g) + g.set_sort_defaults('name') + + def configure_form(self, f): + super(TempmonApplianceView, self).configure_form(f) + + # clients + if self.viewing: + f.set_renderer('clients', self.render_clients) + else: + f.remove_field('clients') + + # probes + if self.viewing: + f.set_renderer('probes', self.render_probes) + elif self.creating or self.editing: + f.remove_field('probes') + + def render_clients(self, appliance, field): + clients = {} + for probe in appliance.probes: + if probe.client.uuid not in clients: + clients[probe.client.uuid] = probe.client + + if not clients: + return "" + + clients = sorted(clients.values(), key=lambda client: client.hostname) + items = [HTML.tag('li', c=[tags.link_to(client.hostname, self.request.route_url('tempmon.clients.view', uuid=client.uuid))]) + for client in clients] + return HTML.tag('ul', c=items) + + +def includeme(config): + TempmonApplianceView.defaults(config) diff --git a/tailbone/views/tempmon/clients.py b/tailbone/views/tempmon/clients.py index 560525f1..dc5d54ce 100644 --- a/tailbone/views/tempmon/clients.py +++ b/tailbone/views/tempmon/clients.py @@ -28,14 +28,11 @@ from __future__ import unicode_literals, absolute_import import subprocess -import six - from rattail_tempmon.db import model as tempmon import colander from webhelpers2.html import HTML, tags -from tailbone import grids from tailbone.views.tempmon import MasterView @@ -68,6 +65,7 @@ class TempmonClientView(MasterView): 'location', 'disk_type', 'delay', + 'appliances', 'probes', 'notes', 'enabled', @@ -119,6 +117,12 @@ class TempmonClientView(MasterView): # delay f.set_helptext('delay', tempmon.Client.delay.__doc__) + # appliances + if self.viewing: + f.set_renderer('appliances', self.render_appliances) + else: + f.remove_field('appliances') + # probes if self.viewing: f.set_renderer('probes', self.render_probes) @@ -149,44 +153,19 @@ class TempmonClientView(MasterView): if query.count(): raise colander.Invalid(node, "Config key must be unique") - def render_probes(self, client, field): - if not client.probes: + def render_appliances(self, client, field): + appliances = {} + for probe in client.probes: + if probe.appliance and probe.appliance.uuid not in appliances: + appliances[probe.appliance.uuid] = probe.appliance + + if not appliances: return "" - route_prefix = self.get_route_prefix() - view_url = lambda p, i: self.request.route_url('tempmon.probes.view', uuid=p.uuid) - actions = [ - grids.GridAction('view', icon='zoomin', url=view_url), - ] - if self.request.has_perm('tempmon.probes.edit'): - url = lambda p, i: self.request.route_url('tempmon.probes.edit', uuid=p.uuid) - actions.append(grids.GridAction('edit', icon='pencil', url=url)) - - g = grids.Grid( - key='{}.probes'.format(route_prefix), - data=client.probes, - columns=[ - 'description', - 'critical_temp_min', - 'good_temp_min', - 'good_temp_max', - 'critical_temp_max', - 'status', - 'enabled', - ], - labels={ - 'critical_temp_min': "Crit. Min", - 'good_temp_min': "Good Min", - 'good_temp_max': "Good Max", - 'critical_temp_max': "Crit. Max", - }, - url=lambda p: self.request.route_url('tempmon.probes.view', uuid=p.uuid), - linked_columns=['description'], - main_actions=actions, - ) - g.set_enum('status', self.enum.TEMPMON_PROBE_STATUS) - g.set_type('enabled', 'boolean') - return HTML.literal(g.render_grid()) + appliances = sorted(appliances.values(), key=lambda a: a.name) + items = [HTML.tag('li', c=[tags.link_to(a.name, self.request.route_url('tempmon.appliances.view', uuid=a.uuid))]) + for a in appliances] + return HTML.tag('ul', c=items) def delete_instance(self, client): # bulk-delete all readings first diff --git a/tailbone/views/tempmon/core.py b/tailbone/views/tempmon/core.py index 6b85944e..03c4b9f1 100644 --- a/tailbone/views/tempmon/core.py +++ b/tailbone/views/tempmon/core.py @@ -28,7 +28,9 @@ from __future__ import unicode_literals, absolute_import from rattail_tempmon.db import Session as RawTempmonSession -from tailbone import views +from webhelpers2.html import HTML + +from tailbone import views, grids from tailbone.db import TempmonSession @@ -40,3 +42,45 @@ class MasterView(views.MasterView): def get_bulk_delete_session(self): return RawTempmonSession() + + def render_probes(self, obj, field): + """ + This method is used by Appliance and Client views. + """ + if not obj.probes: + return "" + + route_prefix = self.get_route_prefix() + view_url = lambda p, i: self.request.route_url('tempmon.probes.view', uuid=p.uuid) + actions = [ + grids.GridAction('view', icon='zoomin', url=view_url), + ] + if self.request.has_perm('tempmon.probes.edit'): + url = lambda p, i: self.request.route_url('tempmon.probes.edit', uuid=p.uuid) + actions.append(grids.GridAction('edit', icon='pencil', url=url)) + + g = grids.Grid( + key='{}.probes'.format(route_prefix), + data=obj.probes, + columns=[ + 'description', + 'critical_temp_min', + 'good_temp_min', + 'good_temp_max', + 'critical_temp_max', + 'status', + 'enabled', + ], + labels={ + 'critical_temp_min': "Crit. Min", + 'good_temp_min': "Good Min", + 'good_temp_max': "Good Max", + 'critical_temp_max': "Crit. Max", + }, + url=lambda p: self.request.route_url('tempmon.probes.view', uuid=p.uuid), + linked_columns=['description'], + main_actions=actions, + ) + g.set_enum('status', self.enum.TEMPMON_PROBE_STATUS) + g.set_type('enabled', 'boolean') + return HTML.literal(g.render_grid()) diff --git a/tailbone/views/tempmon/probes.py b/tailbone/views/tempmon/probes.py index 25542b4c..7256a0a3 100644 --- a/tailbone/views/tempmon/probes.py +++ b/tailbone/views/tempmon/probes.py @@ -61,6 +61,7 @@ class TempmonProbeView(MasterView): grid_columns = [ 'client', 'config_key', + 'appliance', 'appliance_type', 'description', 'device_path', @@ -71,8 +72,10 @@ class TempmonProbeView(MasterView): form_fields = [ 'client', 'config_key', + 'appliance', 'appliance_type', 'description', + 'location', 'device_path', 'critical_temp_max', 'critical_max_timeout', @@ -133,6 +136,18 @@ class TempmonProbeView(MasterView): f.set_widget('client_uuid', dfwidget.SelectWidget(values=client_values)) f.set_label('client_uuid', "Tempmon Client") + # appliance + f.set_renderer('appliance', self.render_appliance) + if self.creating or self.editing: + f.replace('appliance', 'appliance_uuid') + appliances = self.Session.query(tempmon.Appliance)\ + .order_by(tempmon.Appliance.name) + appliance_values = [(appliance.uuid, appliance.name) + for appliance in appliances] + appliance_values.insert(0, ('', "(none)")) + f.set_widget('appliance_uuid', dfwidget.SelectWidget(values=appliance_values)) + f.set_label('appliance_uuid', "Appliance") + # appliance_type f.set_enum('appliance_type', self.enum.TEMPMON_APPLIANCE_TYPE) @@ -167,6 +182,14 @@ class TempmonProbeView(MasterView): url = self.request.route_url('tempmon.clients.view', uuid=client.uuid) return tags.link_to(text, url) + def render_appliance(self, probe, field): + appliance = probe.appliance + if not appliance: + return "" + text = six.text_type(appliance) + url = self.request.route_url('tempmon.appliances.view', uuid=appliance.uuid) + return tags.link_to(text, url) + def delete_instance(self, probe): # bulk-delete all readings first readings = self.Session.query(tempmon.Reading)\