1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Closes #9416: Dashboard widgets (#11823)

* Replace masonry with gridstack

* Initial work on dashboard widgets

* Implement function to save dashboard layout

* Define a default dashboard

* Clean up widgets

* Implement widget configuration views & forms

* Permit merging dict value with existing dict in user config

* Add widget deletion view

* Enable HTMX for widget configuration

* Implement view to add dashboard widgets

* ObjectCountsWidget: Identify models by app_label & name

* Add color customization to dashboard widgets

* Introduce Dashboard model to store user dashboard layout & config

* Clean up utility functions

* Remove hard-coded API URL

* Use fixed grid cell height

* Add modal close button

* Clean up dashboard views

* Rebuild JS
This commit is contained in:
Jeremy Stretch
2023-02-24 16:04:00 -05:00
committed by GitHub
parent 36771e821c
commit 084a2cc52c
40 changed files with 788 additions and 310 deletions

View File

@@ -0,0 +1,37 @@
{% load dashboard %}
<div
class="grid-stack-item"
gs-w="{{ widget.width }}"
gs-h="{{ widget.height }}"
gs-x="{{ widget.x }}"
gs-y="{{ widget.y }}"
gs-id="{{ widget.id }}"
>
<div class="card grid-stack-item-content">
<div class="card-header text-center text-light bg-{% if widget.color %}{{ widget.color }}{% else %}secondary{% endif %} p-1">
<div class="float-start ps-1">
<a href="#"
hx-get="{% url 'extras:dashboardwidget_config' id=widget.id %}"
hx-target="#htmx-modal-content"
data-bs-toggle="modal"
data-bs-target="#htmx-modal"
><i class="mdi mdi-cog text-gray"></i></a>
</div>
<div class="float-end pe-1">
<a href="#"
hx-get="{% url 'extras:dashboardwidget_delete' id=widget.id %}"
hx-target="#htmx-modal-content"
data-bs-toggle="modal"
data-bs-target="#htmx-modal"
><i class="mdi mdi-close text-gray"></i></a>
</div>
{% if widget.title %}
<strong>{{ widget.title }}</strong>
{% endif %}
</div>
<div class="card-body p-2">
{% render_widget widget %}
</div>
</div>
</div>

View File

@@ -0,0 +1,27 @@
{% load form_helpers %}
<form hx-post="{% url 'extras:dashboardwidget_add' %}" id="widget_add_form">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title">Add a Widget</h5>
</div>
<div class="modal-body">
{% block form %}
{% render_field widget_form.widget_class %}
<div class="row mb-3">
<label class="col-sm-3 col-form-label text-lg-end">Description</label>
<div class="col">
<div class="form-control-plaintext">{{ widget_class.description|placeholder }}</div>
</div>
</div>
{% render_field widget_form.color %}
{% render_field widget_form.title %}
{% render_form config_form %}
{% endblock form %}
</div>
<div class="modal-footer">
{% block buttons %}
<button class="btn btn-primary">Save</button>
{% endblock buttons %}
</div>
</form>

View File

@@ -0,0 +1,20 @@
{% load form_helpers %}
<form hx-post="{{ form_url }}">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title">Widget Configuration</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
{% block form %}
{% render_form widget_form %}
{% render_form config_form %}
{% endblock form %}
</div>
<div class="modal-footer">
{% block buttons %}
<button class="btn btn-primary">Save</button>
{% endblock buttons %}
</div>
</form>

View File

@@ -0,0 +1,4 @@
<div class="htmx-container"
hx-get="{% url 'extras:objectchange_list' %}?sort=-time"
hx-trigger="load"
></div>

View File

@@ -0,0 +1,14 @@
{% load helpers %}
{% if counts %}
<div class="list-group list-group-flush">
{% for model, count in counts %}
<a href="{% url model|viewname:"list" %}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between align-items-center">
{{ model|meta:"verbose_name_plural"|bettertitle }}
<h6 class="mb-1">{{ count }}</h6>
</div>
</a>
{% endfor %}
</div>
{% endif %}

View File

@@ -3,80 +3,48 @@
{% load render_table from django_tables2 %}
{% block header %}
{% if new_release %}
{# new_release is set only if the current user is a superuser or staff member #}
<div class="header-alert-container">
<div class="alert alert-info text-center mw-md-50" role="alert">
<h6 class="alert-heading">
<i class="mdi mdi-information-outline"></i><br/>New Release Available
</h6>
<small><a href="{{ new_release.url }}">NetBox v{{ new_release.version }}</a> is available.</small>
<hr class="my-2" />
<small class="mb-0">
<a href="https://docs.netbox.dev/en/stable/installation/upgrading/">Upgrade Instructions</a>
</small>
</div>
</div>
{% endif %}
{% if new_release %}
{# new_release is set only if the current user is a superuser or staff member #}
<div class="header-alert-container">
<div class="alert alert-info text-center mw-md-50" role="alert">
<h6 class="alert-heading">
<i class="mdi mdi-information-outline"></i><br/>New Release Available
</h6>
<small><a href="{{ new_release.url }}">NetBox v{{ new_release.version }}</a> is available.</small>
<hr class="my-2" />
<small class="mb-0">
<a href="https://docs.netbox.dev/en/stable/installation/upgrading/">Upgrade Instructions</a>
</small>
</div>
</div>
{% endif %}
{% endblock %}
{% block title %}Home{% endblock %}
{% block content-wrapper %}
<div class="px-3">
{# General stats #}
<div class="row masonry">
{% for section, items, icon in stats %}
<div class="col col-sm-12 col-lg-6 col-xl-4 my-2 masonry-item">
<div class="card">
<h6 class="card-header text-center">
<i class="mdi mdi-{{ icon }}"></i>
<span class="ms-1">{{ section }}</span>
</h6>
<div class="card-body">
<div class="list-group list-group-flush">
{% for item in items %}
{% if item.permission in perms %}
<a href="{% url item.viewname %}" class="list-group-item list-group-item-action">
<div class="d-flex w-100 justify-content-between align-items-center">
{{ item.label }}
<h4 class="mb-1">{{ item.count }}</h4>
</div>
</a>
{% else %}
<li class="list-group-item list-group-item-action disabled">
<div class="d-flex w-100 justify-content-between align-items-center">
{{ item.label }}
<h4 class="mb-1">
<i title="No permission" class="mdi mdi-lock"></i>
</h4>
</div>
</li>
{% endif %}
{% endfor %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{# Changelog #}
{% if perms.extras.view_objectchange %}
<div class="row my-4 flex-grow-1 changelog-container">
<div class="col">
<div class="card">
<h6 class="card-header text-center">
<i class="mdi mdi-clipboard-clock"></i>
<span class="ms-1">Change Log</span>
</h6>
<div class="card-body htmx-container table-responsive"
hx-get="{% url 'extras:objectchange_list' %}?sort=-time"
hx-trigger="load"
></div>
</div>
</div>
</div>
{% endif %}
{# Render the user's customized dashboard #}
<div class="grid-stack">
{% for widget in dashboard %}
{% include 'extras/dashboard/widget.html' %}
{% endfor %}
</div>
<div class="text-end px-2">
<a href="#"
hx-get="{% url 'extras:dashboardwidget_add' %}"
hx-target="#htmx-modal-content"
data-bs-toggle="modal"
data-bs-target="#htmx-modal"
class="btn btn-success btn-sm"
>
<i class="mdi mdi-plus"></i> Add Widget
</a>
<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
</button>
</div>
{% endblock content-wrapper %}
{% block modals %}
{% include 'inc/htmx_modal.html' %}
{% endblock modals %}

View File

@@ -1,5 +1,5 @@
<div class="modal fade" id="htmx-modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-dialog">
<div class="modal-content" id="htmx-modal-content">
{# Dynamic content goes here #}
</div>