mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
#9416: cleanup & widget improvements
This commit is contained in:
@ -19,6 +19,19 @@ WEBHOOK_EVENT_TYPES = {
|
|||||||
|
|
||||||
# Dashboard
|
# Dashboard
|
||||||
DEFAULT_DASHBOARD = [
|
DEFAULT_DASHBOARD = [
|
||||||
|
{
|
||||||
|
'widget': 'extras.ObjectCountsWidget',
|
||||||
|
'width': 4,
|
||||||
|
'height': 2,
|
||||||
|
'title': 'Organization',
|
||||||
|
'config': {
|
||||||
|
'models': [
|
||||||
|
'dcim.site',
|
||||||
|
'tenancy.tenant',
|
||||||
|
'tenancy.contact',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'widget': 'extras.ObjectCountsWidget',
|
'widget': 'extras.ObjectCountsWidget',
|
||||||
'width': 4,
|
'width': 4,
|
||||||
@ -26,12 +39,53 @@ DEFAULT_DASHBOARD = [
|
|||||||
'title': 'IPAM',
|
'title': 'IPAM',
|
||||||
'config': {
|
'config': {
|
||||||
'models': [
|
'models': [
|
||||||
|
'ipam.vrf',
|
||||||
'ipam.aggregate',
|
'ipam.aggregate',
|
||||||
'ipam.prefix',
|
'ipam.prefix',
|
||||||
|
'ipam.iprange',
|
||||||
'ipam.ipaddress',
|
'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',
|
'widget': 'extras.ObjectCountsWidget',
|
||||||
'width': 4,
|
'width': 4,
|
||||||
@ -41,25 +95,33 @@ DEFAULT_DASHBOARD = [
|
|||||||
'models': [
|
'models': [
|
||||||
'dcim.site',
|
'dcim.site',
|
||||||
'dcim.rack',
|
'dcim.rack',
|
||||||
|
'dcim.devicetype',
|
||||||
'dcim.device',
|
'dcim.device',
|
||||||
]
|
'dcim.cable',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'widget': 'extras.NoteWidget',
|
'widget': 'extras.ObjectCountsWidget',
|
||||||
'width': 4,
|
'width': 4,
|
||||||
'height': 3,
|
'height': 2,
|
||||||
|
'title': 'Virtualization',
|
||||||
'config': {
|
'config': {
|
||||||
'content': 'Welcome to **NetBox**!'
|
'models': [
|
||||||
|
'virtualization.cluster',
|
||||||
|
'virtualization.virtualmachine',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'widget': 'extras.ObjectListWidget',
|
'widget': 'extras.ObjectListWidget',
|
||||||
'width': 12,
|
'width': 12,
|
||||||
'height': 6,
|
'height': 5,
|
||||||
'title': 'Change Log',
|
'title': 'Change Log',
|
||||||
|
'color': 'blue',
|
||||||
'config': {
|
'config': {
|
||||||
'model': 'extras.objectchange',
|
'model': 'extras.objectchange',
|
||||||
|
'page_size': 25,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from netbox.registry import registry
|
from netbox.registry import registry
|
||||||
from utilities.forms import BootstrapMixin, add_blank_choice
|
from utilities.forms import BootstrapMixin, add_blank_choice
|
||||||
@ -33,6 +34,7 @@ class DashboardWidgetAddForm(DashboardWidgetForm):
|
|||||||
'hx-get': reverse_lazy('extras:dashboardwidget_add'),
|
'hx-get': reverse_lazy('extras:dashboardwidget_add'),
|
||||||
'hx-target': '#widget_add_form',
|
'hx-target': '#widget_add_form',
|
||||||
}
|
}
|
||||||
)
|
),
|
||||||
|
label=_('Widget type')
|
||||||
)
|
)
|
||||||
field_order = ('widget_class', 'title', 'color')
|
field_order = ('widget_class', 'title', 'color')
|
||||||
|
@ -54,10 +54,7 @@ def get_dashboard(user):
|
|||||||
|
|
||||||
def get_default_dashboard():
|
def get_default_dashboard():
|
||||||
from extras.models import Dashboard
|
from extras.models import Dashboard
|
||||||
dashboard = Dashboard(
|
dashboard = Dashboard()
|
||||||
layout=[],
|
|
||||||
config={}
|
|
||||||
)
|
|
||||||
for widget in DEFAULT_DASHBOARD:
|
for widget in DEFAULT_DASHBOARD:
|
||||||
id = str(uuid.uuid4())
|
id = str(uuid.uuid4())
|
||||||
dashboard.layout.append({
|
dashboard.layout.append({
|
||||||
@ -70,6 +67,7 @@ def get_default_dashboard():
|
|||||||
dashboard.config[id] = {
|
dashboard.config[id] = {
|
||||||
'class': widget['widget'],
|
'class': widget['widget'],
|
||||||
'title': widget.get('title'),
|
'title': widget.get('title'),
|
||||||
|
'color': widget.get('color'),
|
||||||
'config': widget.get('config', {}),
|
'config': widget.get('config', {}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ from django import forms
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.urls import NoReverseMatch, reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from utilities.forms import BootstrapMixin
|
from utilities.forms import BootstrapMixin
|
||||||
@ -126,14 +127,26 @@ class ObjectListWidget(DashboardWidget):
|
|||||||
model = forms.ChoiceField(
|
model = forms.ChoiceField(
|
||||||
choices=get_content_type_labels
|
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):
|
def render(self, request):
|
||||||
app_label, model_name = self.config['model'].split('.')
|
app_label, model_name = self.config['model'].split('.')
|
||||||
content_type = ContentType.objects.get_by_natural_key(app_label, model_name)
|
content_type = ContentType.objects.get_by_natural_key(app_label, model_name)
|
||||||
viewname = get_viewname(content_type.model_class(), action='list')
|
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, {
|
return render_to_string(self.template_name, {
|
||||||
'viewname': viewname,
|
'viewname': viewname,
|
||||||
|
'htmx_url': htmx_url,
|
||||||
|
'page_size': self.config.get('page_size'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,8 +17,8 @@ class Migration(migrations.Migration):
|
|||||||
name='Dashboard',
|
name='Dashboard',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||||
('layout', models.JSONField()),
|
('layout', models.JSONField(default=list)),
|
||||||
('config', models.JSONField()),
|
('config', models.JSONField(default=dict)),
|
||||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dashboard', to=settings.AUTH_USER_MODEL)),
|
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='dashboard', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -14,8 +14,12 @@ class Dashboard(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='dashboard'
|
related_name='dashboard'
|
||||||
)
|
)
|
||||||
layout = models.JSONField()
|
layout = models.JSONField(
|
||||||
config = models.JSONField()
|
default=list
|
||||||
|
)
|
||||||
|
config = models.JSONField(
|
||||||
|
default=dict
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
pass
|
pass
|
||||||
|
@ -727,6 +727,7 @@ class DashboardWidgetConfigView(LoginRequiredMixin, View):
|
|||||||
config_form = widget.ConfigForm(initial=widget.form_data.get('config'), prefix='config')
|
config_form = widget.ConfigForm(initial=widget.form_data.get('config'), prefix='config')
|
||||||
|
|
||||||
return render(request, self.template_name, {
|
return render(request, self.template_name, {
|
||||||
|
'widget_class': widget.__class__,
|
||||||
'widget_form': widget_form,
|
'widget_form': widget_form,
|
||||||
'config_form': config_form,
|
'config_form': config_form,
|
||||||
'form_url': reverse('extras:dashboardwidget_config', kwargs={'id': id})
|
'form_url': reverse('extras:dashboardwidget_config', kwargs={'id': id})
|
||||||
|
@ -3,10 +3,11 @@
|
|||||||
<form hx-post="{{ form_url }}">
|
<form hx-post="{{ form_url }}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title">Widget Configuration</h5>
|
<h5 class="modal-title">{{ widget_class }} Configuration</h5>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<p>{{ widget_class.description }}</p>
|
||||||
{% block form %}
|
{% block form %}
|
||||||
{% render_form widget_form %}
|
{% render_form widget_form %}
|
||||||
{% render_form config_form %}
|
{% render_form config_form %}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
<div class="htmx-container"
|
{% if htmx_url %}
|
||||||
hx-get="{% url viewname %}"
|
<div class="htmx-container" hx-get="{{ htmx_url }}{% if page_size %}?per_page={{ page_size }}{% endif %}" hx-trigger="load"></div>
|
||||||
hx-trigger="load"
|
{% else %}
|
||||||
></div>
|
<div class="text-danger text-center">
|
||||||
|
<i class="mdi mdi-alert"></i> Unable to load content. Invalid view name: <span class="font-monospace">{{ viewname }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
<i class="mdi mdi-plus"></i> Add Widget
|
<i class="mdi mdi-plus"></i> Add Widget
|
||||||
</a>
|
</a>
|
||||||
<button id="save_dashboard" class="btn btn-primary btn-sm" data-url="{% url 'extras-api:dashboard' %}">
|
<button id="save_dashboard" class="btn btn-primary btn-sm" data-url="{% url 'extras-api:dashboard' %}">
|
||||||
<i class="mdi mdi-content-save-outline"></i> Save
|
<i class="mdi mdi-content-save-outline"></i> Save Layout
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{% endblock content-wrapper %}
|
{% endblock content-wrapper %}
|
||||||
|
Reference in New Issue
Block a user