Skip to content

Commit

Permalink
[feature] Added monitoring of WiFi Client and Sessions #360 #361
Browse files Browse the repository at this point in the history
Closes #360
Closes #361

Co-authored-by: Federico Capoano <[email protected]>
  • Loading branch information
pandafy and nemesifier authored May 17, 2022
1 parent 918231c commit f1d4c94
Show file tree
Hide file tree
Showing 21 changed files with 1,216 additions and 13 deletions.
84 changes: 81 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ Available Features
mobile signal (LTE/UMTS/GSM `signal strength <#mobile-signal-strength>`_,
`signal quality <#mobile-signal-quality>`_,
`access technology in use <#mobile-access-technology-in-use>`_)
* Maintains a record of `WiFi sessions <#monitoring-wifi-sessions>`_ with clients'
MAC address and vendor, session start and stop time and connected device
along with other information
* Charts can be viewed at resolutions of 1 day, 3 days, a week, a month and a year
* Configurable alerts
* CSV Export of monitoring data
Expand Down Expand Up @@ -371,6 +374,11 @@ Configure celery (you may use a different broker if you want):
'task': 'openwisp_monitoring.check.tasks.run_checks',
'schedule': timedelta(minutes=5),
},
# Delete old WifiSession
'delete_wifi_clients_and_sessions': {
'task': 'openwisp_monitoring.monitoring.tasks.delete_wifi_clients_and_sessions',
'schedule': timedelta(days=180),
},
}
INSTALLED_APPS.append('djcelery_email')
Expand Down Expand Up @@ -791,6 +799,54 @@ Mobile Access Technology in use
.. figure:: https://github.com/openwisp/openwisp-monitoring/raw/docs/docs/access-technology.png
:align: center

Monitoring WiFi Sessions
------------------------

OpenWISP Monitoring maintains a record of WiFi sessions created by clients
joined to a radio of managed devices. The WiFi sessions are created
asynchronously from the monitoring data received from the device.

You can filter both currently open sessions and past sessions by their
*start* or *stop* time or *organization* or *group* of the device clients
are connected to or even directly by a *device* name or ID.

.. figure:: https://github.com/openwisp/openwisp-monitoring/raw/docs/docs/wifi-session-changelist.png
:align: center

.. figure:: https://github.com/openwisp/openwisp-monitoring/raw/docs/docs/wifi-session-change.png
:align: center

You can disable this feature by configuring
`OPENWISP_MONITORING_WIFI_SESSIONS_ENABLED <#openwisp_monitoring_wifi_sessions_enabled>`_
setting.

Scheduled deletion of WiFi sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

OpenWISP Monitoring provides a celery task to automatically delete
WiFi sessions older than a pre-configured number of days. In order to run this
task periodically, you will need to configure ``CELERY_BEAT_SCHEDULE`` setting as shown
in `setup instructions <#setup-integrate-in-an-existing-django-project>`_.

The celery task takes only one argument, i.e. number of days. You can provide
any number of days in `args` key while configuring ``CELERY_BEAT_SCHEDULE`` setting.

E.g., if you want WiFi Sessions older than 30 days to get deleted automatically,
then configure ``CELERY_BEAT_SCHEDULE`` as follows:

.. code-block:: python
CELERY_BEAT_SCHEDULE = {
'delete_wifi_clients_and_sessions': {
'task': 'openwisp_monitoring.monitoring.tasks.delete_wifi_clients_and_sessions',
'schedule': timedelta(days=1),
'args': (30,), # Here we have defined 30 instead of 180 as shown in setup instructions
},
}
Please refer to `"Periodic Tasks" section of Celery's documentation <https://docs.celeryproject.org/en/stable/userguide/periodic-tasks.html>`_
to learn more.

Default Alerts / Notifications
------------------------------

Expand Down Expand Up @@ -969,6 +1025,18 @@ you can use the following configuration:
'critical': 'offline'
}
``OPENWISP_MONITORING_WIFI_SESSIONS_ENABLED``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+--------------+-------------+
| **type**: | ``bool`` |
+--------------+-------------+
| **default**: | ``True`` |
+--------------+-------------+

Setting this to ``False`` will disable `Monitoring Wifi Sessions <#monitoring-wifi-sessions>`_
feature.

``OPENWISP_MONITORING_MANAGEMENT_IP_ONLY``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down Expand Up @@ -1377,8 +1445,8 @@ An example usage has been shown below.
'scale': [
[[0, '#c13000'],
[0.1,'cb7222'],
[0.5,'#deed0e'],
[0.9, '#7db201'],
[0.5,'#deed0e'],
[0.9, '#7db201'],
[1, '#498b26']],
],
'map': [
Expand Down Expand Up @@ -1981,6 +2049,8 @@ Add the following to your ``settings.py``:
# For extending device_monitoring app
DEVICE_MONITORING_DEVICEDATA_MODEL = 'YOUR_MODULE_NAME.DeviceData'
DEVICE_MONITORING_DEVICEMONITORING_MODEL = 'YOUR_MODULE_NAME.DeviceMonitoring'
DEVICE_MONITORING_WIFICLIENT_MODEL = 'YOUR_MODULE_NAME.WifiClient'
DEVICE_MONITORING_WIFISESSION_MODEL = 'YOUR_MODULE_NAME.WifiSession'
Substitute ``<YOUR_MODULE_NAME>`` with your actual django app name
(also known as ``app_label``).
Expand Down Expand Up @@ -2028,10 +2098,11 @@ Similarly for ``device_monitoring`` app, you can do it as:

.. code-block:: python
from openwisp_monitoring.device.admin import DeviceAdmin
from openwisp_monitoring.device.admin import DeviceAdmin, WifiSessionAdmin
DeviceAdmin.list_display.insert(1, 'my_custom_field')
DeviceAdmin.ordering = ['-my_custom_field']
WifiSessionAdmin.fields += ['my_custom_field']
Similarly for ``monitoring`` app, you can do it as:

Expand Down Expand Up @@ -2074,16 +2145,23 @@ For ``device_monitoring`` app,
from django.contrib import admin
from openwisp_monitoring.device_monitoring.admin import DeviceAdmin as BaseDeviceAdmin
from openwisp_monitoring.device_monitoring.admin import WifiSessionAdmin as BaseWifiSessionAdmin
from swapper import load_model
Device = load_model('config', 'Device')
WifiSession = load_model('device_monitoring', 'WifiSession')
admin.site.unregister(Device)
admin.site.unregister(WifiSession)
@admin.register(Device)
class DeviceAdmin(BaseDeviceAdmin):
# add your changes here
@admin.register(WifiSession)
class WifiSessionAdmin(BaseWifiSessionAdmin):
# add your changes here
For ``monitoring`` app,

.. code-block:: python
Expand Down
149 changes: 149 additions & 0 deletions openwisp_monitoring/device/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from django.contrib.contenttypes.admin import GenericStackedInline
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.forms import ModelForm
from django.templatetags.static import static
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
Expand All @@ -18,19 +20,24 @@
from swapper import load_model

from openwisp_controller.config.admin import DeviceAdmin as BaseDeviceAdmin
from openwisp_users.multitenancy import MultitenantAdminMixin, MultitenantOrgFilter
from openwisp_utils.admin import ReadOnlyAdmin
from openwisp_utils.admin_theme.filters import SimpleInputFilter

from ..monitoring.admin import MetricAdmin
from ..settings import MONITORING_API_BASEURL, MONITORING_API_URLCONF
from . import settings as app_settings

DeviceData = load_model('device_monitoring', 'DeviceData')
WifiSession = load_model('device_monitoring', 'WifiSession')
DeviceMonitoring = load_model('device_monitoring', 'DeviceMonitoring')
AlertSettings = load_model('monitoring', 'AlertSettings')
Chart = load_model('monitoring', 'Chart')
Device = load_model('config', 'Device')
Metric = load_model('monitoring', 'Metric')
Notification = load_model('openwisp_notifications', 'Notification')
Check = load_model('check', 'Check')
Organization = load_model('openwisp_users', 'Organization')


class CheckInlineFormSet(BaseGenericInlineFormSet):
Expand Down Expand Up @@ -216,3 +223,145 @@ def get_inlines(self, request, obj=None):

admin.site.unregister(Device)
admin.site.register(Device, DeviceAdmin)


class DeviceFilter(SimpleInputFilter):
"""
Filters WifiSession queryset for input device name
or primary key
"""

parameter_name = 'device'
title = _('device name or ID')

def queryset(self, request, queryset):
if self.value() is not None:
try:
uuid.UUID(self.value())
except ValueError:
lookup = Q(device__name=self.value())
else:
lookup = Q(device_id=self.value())
return queryset.filter(lookup)


class WifiSessionAdmin(MultitenantAdminMixin, ReadOnlyAdmin):
multitenant_parent = 'device'
model = WifiSession
list_display = [
'mac_address',
'vendor',
'related_organization',
'related_device',
'ssid',
'ht',
'vht',
'start_time',
'get_stop_time',
]
fields = [
'related_organization',
'mac_address',
'vendor',
'related_device',
'ssid',
'interface_name',
'ht',
'vht',
'wmm',
'wds',
'wps',
'start_time',
'get_stop_time',
'modified',
]
search_fields = ['wifi_client__mac_address', 'device__name', 'device__mac_address']
list_filter = [
('device__organization', MultitenantOrgFilter),
'start_time',
'stop_time',
'device__group',
DeviceFilter,
]

def get_readonly_fields(self, request, obj=None):
fields = super().get_readonly_fields(request, obj)
fields += [
'related_organization',
'mac_address',
'vendor',
'ht',
'vht',
'wmm',
'wds',
'wps',
'get_stop_time',
'modified',
'related_device',
]
return fields

def get_queryset(self, request):
return (
super()
.get_queryset(request)
.select_related(
'wifi_client', 'device', 'device__organization', 'device__group'
)
)

def _get_boolean_html(self, value):
icon = static('admin/img/icon-{}.svg'.format('yes' if value is True else 'no'))
return mark_safe(f'<img src="{icon}">')

def ht(self, obj):
return self._get_boolean_html(obj.wifi_client.ht)

ht.short_description = 'HT'

def vht(self, obj):
return self._get_boolean_html(obj.wifi_client.vht)

vht.short_description = 'VHT'

def wmm(self, obj):
return self._get_boolean_html(obj.wifi_client.wmm)

wmm.short_description = 'WMM'

def wds(self, obj):
return self._get_boolean_html(obj.wifi_client.wds)

wds.short_description = 'WDS'

def wps(self, obj):
return self._get_boolean_html(obj.wifi_client.wps)

wps.short_description = 'WPS'

def get_stop_time(self, obj):
if obj.stop_time is None:
return mark_safe('<strong style="color:green;">online</strong>')
return obj.stop_time

get_stop_time.short_description = _('stop time')

def related_device(self, obj):
app_label = Device._meta.app_label
url = reverse(f'admin:{app_label}_device_change', args=[obj.device_id])
return mark_safe(f'<a href="{url}">{obj.device}</a>')

related_device.short_description = _('device')

def related_organization(self, obj):
app_label = Organization._meta.app_label
url = reverse(
f'admin:{app_label}_organization_change', args=[obj.organization.id]
)
return mark_safe(f'<a href="{url}">{obj.organization}</a>')

related_organization.short_description = _('organization')


if app_settings.WIFI_SESSIONS_ENABLED:
admin.site.register(WifiSession, WifiSessionAdmin)
Loading

0 comments on commit f1d4c94

Please sign in to comment.