diff --git a/netbox/extras/apps.py b/netbox/extras/apps.py index 965eb033e..f23e62dd2 100644 --- a/netbox/extras/apps.py +++ b/netbox/extras/apps.py @@ -5,4 +5,4 @@ class ExtrasConfig(AppConfig): name = "extras" def ready(self): - from . import lookups, search, signals + from . import dashboard, lookups, search, signals diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index d65fb9612..2a47c15e2 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -1,2 +1,47 @@ # Webhook content types HTTP_CONTENT_TYPE_JSON = 'application/json' + +# Dashboard +DEFAULT_DASHBOARD = [ + { + 'widget': 'extras.ObjectCountsWidget', + 'width': 4, + 'height': 3, + 'config': { + 'title': 'IPAM', + 'models': [ + 'ipam.Aggregate', + 'ipam.Prefix', + 'ipam.IPRange', + 'ipam.IPAddress', + ] + } + }, + { + 'widget': 'extras.ObjectCountsWidget', + 'width': 4, + 'height': 3, + 'config': { + 'title': 'DCIM', + 'models': [ + 'dcim.Site', + 'dcim.Rack', + 'dcim.Device', + 'dcim.Cable', + ] + } + }, + { + 'widget': 'extras.StaticContentWidget', + 'width': 4, + 'height': 3, + 'config': { + 'content': 'Welcome to NetBox!' + } + }, + { + 'widget': 'extras.ChangeLogWidget', + 'width': 12, + 'height': 6, + }, +] diff --git a/netbox/extras/dashboard/__init__.py b/netbox/extras/dashboard/__init__.py new file mode 100644 index 000000000..2539f0cbe --- /dev/null +++ b/netbox/extras/dashboard/__init__.py @@ -0,0 +1,2 @@ +from .utils import * +from .widgets import * diff --git a/netbox/extras/dashboard/utils.py b/netbox/extras/dashboard/utils.py new file mode 100644 index 000000000..1da34b72f --- /dev/null +++ b/netbox/extras/dashboard/utils.py @@ -0,0 +1,66 @@ +import uuid + +from netbox.registry import registry +from extras.constants import DEFAULT_DASHBOARD + +__all__ = ( + 'get_dashboard', + 'register_widget', +) + + +def register_widget(cls): + """ + Decorator for registering a DashboardWidget class. + """ + app_label = cls.__module__.split('.', maxsplit=1)[0] + label = f'{app_label}.{cls.__name__}' + registry['widgets'][label] = cls + + return cls + + +def get_dashboard(user): + """ + Return the dashboard layout for a given User. + """ + if not user.is_anonymous and user.config.get('dashboard'): + config = user.config.get('dashboard') + else: + config = get_default_dashboard_config() + print(config) + if not user.is_anonymous: + user.config.set('dashboard', config, commit=True) + + widgets = [] + for grid_item in config['layout']: + widget_id = grid_item['id'] + widget_config = config['widgets'][widget_id] + widget_class = registry['widgets'].get(widget_config.pop('class')) + widget = widget_class(id=widget_id, **widget_config) + widget.set_layout(grid_item) + widgets.append(widget) + + return widgets + + +def get_default_dashboard_config(): + config = { + 'layout': [], + 'widgets': {}, + } + for widget in DEFAULT_DASHBOARD: + id = str(uuid.uuid4()) + config['layout'].append({ + 'id': id, + 'w': widget['width'], + 'h': widget['height'], + 'x': widget.get('x'), + 'y': widget.get('y'), + }) + config['widgets'][id] = { + 'class': widget['widget'], + 'config': widget.get('config', {}), + } + + return config diff --git a/netbox/extras/dashboard.py b/netbox/extras/dashboard/widgets.py similarity index 88% rename from netbox/extras/dashboard.py rename to netbox/extras/dashboard/widgets.py index 557452af1..776e75d90 100644 --- a/netbox/extras/dashboard.py +++ b/netbox/extras/dashboard/widgets.py @@ -4,28 +4,16 @@ from django.contrib.contenttypes.models import ContentType from django.template.loader import render_to_string from django.utils.translation import gettext as _ -from netbox.registry import registry - +from .utils import register_widget __all__ = ( 'ChangeLogWidget', 'DashboardWidget', 'ObjectCountsWidget', 'StaticContentWidget', - 'register_widget', ) -def register_widget(cls): - """ - Decorator for registering a DashboardWidget class. - """ - label = f'{cls.__module__}.{cls.__name__}' - registry['widgets'][label] = cls - - return cls - - class DashboardWidget: width = 4 height = 3 @@ -41,6 +29,12 @@ class DashboardWidget: self.height = height self.x, self.y = x, y + def set_layout(self, grid_item): + self.width = grid_item['w'] + self.height = grid_item['h'] + self.x = grid_item.get('x') + self.y = grid_item.get('y') + def render(self, request): raise NotImplementedError("DashboardWidget subclasses must define a render() method.") diff --git a/netbox/netbox/views/misc.py b/netbox/netbox/views/misc.py index 337862f78..7e0e1490a 100644 --- a/netbox/netbox/views/misc.py +++ b/netbox/netbox/views/misc.py @@ -9,7 +9,7 @@ from django.views.generic import View from django_tables2 import RequestConfig from packaging import version -from extras import dashboard +from extras.dashboard.utils import get_dashboard from netbox.forms import SearchForm from netbox.registry import registry from netbox.search import LookupTypes @@ -34,19 +34,7 @@ class HomeView(View): return redirect('login') # Build custom dashboard from user's config - widgets = [] - for grid_item in request.user.config.get('dashboard.layout'): - config = request.user.config.get(f"dashboard.widgets.{grid_item['id']}") - widget_class = registry['widgets'].get(config.pop('class')) - widget = widget_class( - id=grid_item.get('id'), - width=grid_item['w'], - height=grid_item['h'], - x=grid_item['x'], - y=grid_item['y'], - **config - ) - widgets.append(widget) + widgets = get_dashboard(request.user) # Check whether a new release is available. (Only for staff/superusers.) new_release = None