diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 2fb43f260..6d9f78001 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -19,6 +19,19 @@ WEBHOOK_EVENT_TYPES = { # Dashboard DEFAULT_DASHBOARD = [ + { + 'widget': 'extras.ObjectCountsWidget', + 'width': 4, + 'height': 2, + 'title': 'Organization', + 'config': { + 'models': [ + 'dcim.site', + 'tenancy.tenant', + 'tenancy.contact', + ] + } + }, { 'widget': 'extras.ObjectCountsWidget', 'width': 4, @@ -26,12 +39,53 @@ DEFAULT_DASHBOARD = [ 'title': 'IPAM', 'config': { 'models': [ + 'ipam.vrf', 'ipam.aggregate', 'ipam.prefix', + 'ipam.iprange', 'ipam.ipaddress', + 'ipam.vlan', ] } }, + { + 'widget': 'extras.NoteWidget', + 'width': 4, + 'height': 2, + 'title': 'Welcome!', + 'color': 'green', + 'config': { + 'content': ( + 'This is your personal dashboard. Feel free to customize it by rearranging, resizing, or removing ' + 'widgets. You can also add new widgets using the "add widget" button below. Any changes affect only ' + '_your_ dashboard, so feel free to experiment!' + ) + } + }, + { + 'widget': 'extras.ObjectCountsWidget', + 'width': 4, + 'height': 2, + 'title': 'Circuits', + 'config': { + 'models': [ + 'circuits.provider', + 'circuits.circuit', + 'circuits.providernetwork', + ] + } + }, + { + 'widget': 'extras.RSSFeedWidget', + 'width': 4, + 'height': 4, + 'title': 'NetBox News', + 'config': { + 'feed_url': 'http://netbox.dev/rss/', + 'max_entries': 10, + 'cache_timeout': 14400, + } + }, { 'widget': 'extras.ObjectCountsWidget', 'width': 4, @@ -41,25 +95,33 @@ DEFAULT_DASHBOARD = [ 'models': [ 'dcim.site', 'dcim.rack', + 'dcim.devicetype', 'dcim.device', - ] + 'dcim.cable', + ], } }, { - 'widget': 'extras.NoteWidget', + 'widget': 'extras.ObjectCountsWidget', 'width': 4, - 'height': 3, + 'height': 2, + 'title': 'Virtualization', 'config': { - 'content': 'Welcome to **NetBox**!' + 'models': [ + 'virtualization.cluster', + 'virtualization.virtualmachine', + ] } }, { 'widget': 'extras.ObjectListWidget', 'width': 12, - 'height': 6, + 'height': 5, 'title': 'Change Log', + 'color': 'blue', 'config': { 'model': 'extras.objectchange', + 'page_size': 25, } }, ] diff --git a/netbox/extras/dashboard/forms.py b/netbox/extras/dashboard/forms.py index ba07be4b1..1e9f15408 100644 --- a/netbox/extras/dashboard/forms.py +++ b/netbox/extras/dashboard/forms.py @@ -1,5 +1,6 @@ from django import forms from django.urls import reverse_lazy +from django.utils.translation import gettext as _ from netbox.registry import registry from utilities.forms import BootstrapMixin, add_blank_choice @@ -33,6 +34,7 @@ class DashboardWidgetAddForm(DashboardWidgetForm): 'hx-get': reverse_lazy('extras:dashboardwidget_add'), 'hx-target': '#widget_add_form', } - ) + ), + label=_('Widget type') ) field_order = ('widget_class', 'title', 'color') diff --git a/netbox/extras/dashboard/utils.py b/netbox/extras/dashboard/utils.py index 8281cc522..abd731464 100644 --- a/netbox/extras/dashboard/utils.py +++ b/netbox/extras/dashboard/utils.py @@ -54,10 +54,7 @@ def get_dashboard(user): def get_default_dashboard(): from extras.models import Dashboard - dashboard = Dashboard( - layout=[], - config={} - ) + dashboard = Dashboard() for widget in DEFAULT_DASHBOARD: id = str(uuid.uuid4()) dashboard.layout.append({ @@ -70,6 +67,7 @@ def get_default_dashboard(): dashboard.config[id] = { 'class': widget['widget'], 'title': widget.get('title'), + 'color': widget.get('color'), 'config': widget.get('config', {}), } diff --git a/netbox/extras/dashboard/widgets.py b/netbox/extras/dashboard/widgets.py index a73c3d759..13cc60af0 100644 --- a/netbox/extras/dashboard/widgets.py +++ b/netbox/extras/dashboard/widgets.py @@ -7,6 +7,7 @@ from django import forms from django.contrib.contenttypes.models import ContentType from django.core.cache import cache from django.template.loader import render_to_string +from django.urls import NoReverseMatch, reverse from django.utils.translation import gettext as _ from utilities.forms import BootstrapMixin @@ -126,14 +127,26 @@ class ObjectListWidget(DashboardWidget): model = forms.ChoiceField( choices=get_content_type_labels ) + page_size = forms.IntegerField( + required=False, + min_value=1, + max_value=100, + help_text=_('The default number of objects to display') + ) def render(self, request): app_label, model_name = self.config['model'].split('.') content_type = ContentType.objects.get_by_natural_key(app_label, model_name) viewname = get_viewname(content_type.model_class(), action='list') + try: + htmx_url = reverse(viewname) + except NoReverseMatch: + htmx_url = None return render_to_string(self.template_name, { 'viewname': viewname, + 'htmx_url': htmx_url, + 'page_size': self.config.get('page_size'), }) diff --git a/netbox/extras/migrations/0087_dashboard.py b/netbox/extras/migrations/0087_dashboard.py index e64843e0e..0c048c5ef 100644 --- a/netbox/extras/migrations/0087_dashboard.py +++ b/netbox/extras/migrations/0087_dashboard.py @@ -17,8 +17,8 @@ class Migration(migrations.Migration): name='Dashboard', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)), - ('layout', models.JSONField()), - ('config', models.JSONField()), + ('layout', models.JSONField(default=list)), + ('config', models.JSONField(default=dict)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dashboard', to=settings.AUTH_USER_MODEL)), ], ), diff --git a/netbox/extras/models/dashboard.py b/netbox/extras/models/dashboard.py index cdbf85b60..a0d300ff6 100644 --- a/netbox/extras/models/dashboard.py +++ b/netbox/extras/models/dashboard.py @@ -14,8 +14,12 @@ class Dashboard(models.Model): on_delete=models.CASCADE, related_name='dashboard' ) - layout = models.JSONField() - config = models.JSONField() + layout = models.JSONField( + default=list + ) + config = models.JSONField( + default=dict + ) class Meta: pass diff --git a/netbox/extras/views.py b/netbox/extras/views.py index daf435861..8677c505e 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -727,6 +727,7 @@ class DashboardWidgetConfigView(LoginRequiredMixin, View): config_form = widget.ConfigForm(initial=widget.form_data.get('config'), prefix='config') return render(request, self.template_name, { + 'widget_class': widget.__class__, 'widget_form': widget_form, 'config_form': config_form, 'form_url': reverse('extras:dashboardwidget_config', kwargs={'id': id}) diff --git a/netbox/templates/extras/dashboard/widget_config.html b/netbox/templates/extras/dashboard/widget_config.html index 6f8f8cc20..58696b863 100644 --- a/netbox/templates/extras/dashboard/widget_config.html +++ b/netbox/templates/extras/dashboard/widget_config.html @@ -3,10 +3,11 @@
{% csrf_token %} {% endblock content-wrapper %}