Skip to content

Commit 57b0ded

Browse files
committed
[feature] Add Monitoring Checks section in Device Admin #53
Closes #53
1 parent 43739e6 commit 57b0ded

File tree

4 files changed

+82
-3
lines changed

4 files changed

+82
-3
lines changed

openwisp_monitoring/device/admin.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import uuid
22

33
from django.contrib import admin
4+
from django.contrib.contenttypes.admin import GenericStackedInline
5+
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
6+
from django.contrib.contenttypes.models import ContentType
7+
from django.db.models import TextField
8+
from django.forms import Textarea
49
from django.urls import reverse
510
from django.utils.html import format_html
611
from django.utils.safestring import mark_safe
712
from django.utils.translation import gettext_lazy as _
813
from swapper import load_model
914

1015
from openwisp_controller.config.admin import DeviceAdmin as BaseDeviceAdmin
16+
from openwisp_utils.admin import TimeReadonlyAdminMixin
1117

1218
from ..monitoring.admin import MetricAdmin
1319
from . import settings as app_settings
@@ -16,6 +22,36 @@
1622
DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring')
1723
Chart = load_model('monitoring', 'Chart')
1824
Device = load_model('config', 'Device')
25+
Check = load_model('check', 'Check')
26+
27+
28+
class CheckInlineFormSet(BaseGenericInlineFormSet):
29+
def full_clean(self):
30+
for form in self.forms:
31+
obj = form.instance
32+
if not obj.content_type or not obj.object_id:
33+
setattr(
34+
form.instance,
35+
self.ct_field.get_attname(),
36+
ContentType.objects.get_for_model(self.instance).pk,
37+
)
38+
setattr(form.instance, self.ct_fk_field.get_attname(), self.instance.pk)
39+
super().full_clean()
40+
41+
42+
class CheckInline(TimeReadonlyAdminMixin, GenericStackedInline):
43+
model = Check
44+
extra = 0
45+
formset = CheckInlineFormSet
46+
fieldsets = [
47+
(
48+
None,
49+
{'fields': ('name', 'check', 'active', 'params', 'created', 'modified',)},
50+
),
51+
]
52+
formfield_overrides = {
53+
TextField: {'widget': Textarea(attrs={'rows': 3, 'cols': 40})},
54+
}
1955

2056

2157
class DeviceAdmin(BaseDeviceAdmin):
@@ -56,6 +92,8 @@ def get_form(self, request, obj=None, **kwargs):
5692
return super().get_form(request, obj, **kwargs)
5793

5894

95+
DeviceAdmin.inlines.append(CheckInline)
96+
5997
DeviceAdmin.Media.js += MetricAdmin.Media.js + ('monitoring/js/percircle.js',)
6098
DeviceAdmin.Media.css['all'] += (
6199
'monitoring/css/percircle.css',

openwisp_monitoring/device/tests/test_admin.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
from django.contrib.auth import get_user_model
2+
from django.contrib.contenttypes.forms import generic_inlineformset_factory
23
from django.urls import reverse
3-
from swapper import load_model
4+
from django.utils.timezone import now
5+
from swapper import get_model_name, load_model
46

7+
from ...check.settings import CHECK_CLASSES
8+
from ..admin import CheckInline, CheckInlineFormSet
59
from . import DeviceMonitoringTestCase
610

711
Chart = load_model('monitoring', 'Chart')
812
Metric = load_model('monitoring', 'Metric')
913
DeviceData = load_model('device_monitoring', 'DeviceData')
1014
User = get_user_model()
15+
Check = load_model('check', 'Check')
1116

1217

1318
class TestAdmin(DeviceMonitoringTestCase):
@@ -21,14 +26,20 @@ def _login_admin(self):
2126

2227
def test_device_admin(self):
2328
dd = self.create_test_adata()
29+
check = Check.objects.create(
30+
name='Ping check', check=CHECK_CLASSES[0][0], content_object=dd, params={},
31+
)
2432
url = reverse('admin:config_device_change', args=[dd.pk])
2533
self._login_admin()
2634
r = self.client.get(url)
2735
self.assertContains(r, '<h2>Status</h2>')
2836
self.assertContains(r, '<h2>Charts</h2>')
37+
self.assertContains(r, '<h2>Checks</h2>')
2938
self.assertContains(r, 'Storage')
3039
self.assertContains(r, 'CPU')
3140
self.assertContains(r, 'RAM status')
41+
self.assertContains(r, check.name)
42+
self.assertContains(r, check.params)
3243

3344
def test_no_device_data(self):
3445
d = self._create_device(organization=self._create_org())
@@ -64,3 +75,35 @@ def test_uuid_bug(self):
6475
self._login_admin()
6576
r = self.client.get(url)
6677
self.assertContains(r, '<h2>Status</h2>')
78+
79+
def test_check_inline_formset(self):
80+
d = self._create_device(organization=self._create_org())
81+
check_inline_formset = generic_inlineformset_factory(
82+
model=Check, form=CheckInline.form, formset=CheckInlineFormSet
83+
)
84+
# model_name changes if swapped
85+
model_name = get_model_name('check', 'Check').lower().replace('.', '-')
86+
ct = f'{model_name}-content_type-object_id'
87+
data = {
88+
f'{ct}-TOTAL_FORMS': '1',
89+
f'{ct}-INITIAL_FORMS': '0',
90+
f'{ct}-MAX_NUM_FORMS': '0',
91+
f'{ct}-0-name': 'Ping Check',
92+
f'{ct}-0-check': CHECK_CLASSES[0][0],
93+
f'{ct}-0-params': '{}',
94+
f'{ct}-0-active': True,
95+
f'{ct}-0-created': now(),
96+
f'{ct}-0-modified': now(),
97+
}
98+
formset = check_inline_formset(data)
99+
formset.instance = d
100+
self.assertTrue(formset.is_valid())
101+
self.assertEqual(formset.errors, [{}])
102+
self.assertEqual(formset.non_form_errors(), [])
103+
form = formset.forms[0]
104+
form.cleaned_data = data
105+
form.save(commit=True)
106+
self.assertEqual(Check.objects.count(), 1)
107+
c = Check.objects.first()
108+
self.assertEqual(c.name, 'Ping Check')
109+
self.assertEqual(c.content_object, d)

openwisp_monitoring/device/tests/test_models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from paramiko.ssh_exception import NoValidConnectionsError
99
from swapper import load_model
1010

11-
from openwisp_controller.connection.models import Credentials, DeviceConnection
1211
from openwisp_controller.connection.tests.base import CreateConnectionsMixin
1312
from openwisp_utils.tests import catch_signal
1413

openwisp_monitoring/monitoring/tests/test_admin.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77

88
class TestAdmin(TestMonitoringMixin, TestCase):
9-
109
def _login_admin(self):
1110
User = get_user_model()
1211
u = User.objects.create_superuser('admin', 'admin', '[email protected]')

0 commit comments

Comments
 (0)