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

Closes #12128: Refresh the web UI to employ the Tabler CSS framework (#14833)

* Remove dark mode styling

* Condense & rename light mode stylesheet

* Upgrade to Bootstrap 5.3.2

* Swap out Bootstrap for Tabler; remove custom styling

* Update base page layout for Tabler

* Update login page

* Bump node to v18

* Update button styles

* Update object list view

* Tweak navbar size

* Clean up dashboard widgets

* Ditch separate stylesheet for print media

* Remove simplebar

* Remove obsolete sidebar styling

* Clean up object view template

* Clean up object edit template

* Standardize primary button sizing

* Clean up object list styling

* Add buttons for add & import to navigation menu

* Fix global search bar

* Fix slim-select form widget styling

* Fix toast styling

* Set base fonts

* Clean up paginator styling

* Clean up navigation menu group headings

* Clean up footer links

* Clean up card styles

* Move SVG styles to a designated directory

* Restructure SCSS files

* Remove obsolete/redundant dependencies

* Fix icon spacing

* Update background color classes

* Tweak banner & footer styling and spacing

* Fix badge background colors in table content

* Bump @types/bootstrap to 5.2.10

* Clean up form layouts

* Fix object selector button style

* Fix icon padding inside small buttons

* Fix icon & badge spacing inside buttons and tabs

* Hide paginator for empty pages

* Fix hover color for list items (Tabler bug #1694)

* Fix width of checkbox column in empty tables

* Clean up bulk edit template

* Fix border color of reslug button

* Package & serve Google fonts locally

* Fix tab styling

* Reduce vetical space at top of dashboard

* Remove obsolete content-wrapper template block

* Fix icon spacing in dropdown menu items

* Fix color label sizing

* Separate bulk delete form & object list into tabs

* Fix styling of filter group headings

* Fix styling for object changelog & journal views

* Standardize ordering & styling of action buttons

* Fix designation of active menu item

* Automatically expand menu section containing the active link

* Clean up nav menu styling

* Remove button colors; hide buttons except on hover/active

* Highlight menu group containing the active item

* Update & standardize alert styling

* Refactor base templates to ensure consistent display of header content

* Tweak styling for links inside badges

* Clean up top menu

* Fix JSON/YAML toggles for config context data

* Fix object template header

* Constrain tabs to container-xl; tweak header margins

* Fix object identifier styling

* Fix positioning of card header buttons

* Remove padding from HTMX tables inside cards

* Ensure consistent use of row headings in attribute tables

* Remove padding surrounding tables inside cards

* Remove obsolete CSS classes

* Misc cleanup of old styling

* Refactor 'controls' template block; ditch old classes

* Fix login button sizing

* Limit object edit form width

* Append asterisk to required form field labels

* Remove obsolete styling

* Remove obsolete styling

* Fix position of progress bar outside label

* Fix alignment of delete button in report/script lists

* Fix <pre> styling

* Clean up page headers

* Replace SVG icons with Material Design icons

* Restore dark mode togle functionality

* Fix top navbar background color under dark mode

* Rebuild static assets
This commit is contained in:
Jeremy Stretch
2024-01-17 16:25:42 -05:00
committed by GitHub
parent 8254e707b6
commit 073c2dc8ca
384 changed files with 11579 additions and 10019 deletions

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<div class="alert alert-danger bg-danger-subtle mx-3 my-2" role="alert">
<div class="d-flex">
<div>
<i class="mdi mdi-alert-octagon p-2"></i>
</div>
<div>
<h4 class="alert-title">{% block title %}{% trans title %}{% endblock %}</h4>
{% block content %}
<div class="text-secondary">{% trans message %}</div>
{% endblock content %}
</div>
</div>
</div>

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<div class="alert alert-info bg-info-subtle mx-3 my-2" role="alert">
<div class="d-flex">
<div>
<i class="mdi mdi-information-outline p-2"></i>
</div>
<div>
<h4 class="alert-title">{% block title %}{% trans title %}{% endblock %}</h4>
{% block content %}
<div class="text-secondary">{% trans message %}</div>
{% endblock content %}
</div>
</div>
</div>

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<div class="alert alert-success bg-success-subtle mx-3 my-2" role="alert">
<div class="d-flex">
<div>
<i class="mdi mdi-check-bold p-2"></i>
</div>
<div>
<h4 class="alert-title">{% block title %}{% trans title %}{% endblock %}</h4>
{% block content %}
<div class="text-secondary">{% trans message %}</div>
{% endblock content %}
</div>
</div>
</div>

View File

@@ -0,0 +1,15 @@
{% load i18n %}
<div class="alert alert-warning bg-warning-subtle mx-3 my-2" role="alert">
<div class="d-flex">
<div>
<i class="mdi mdi-alert p-2"></i>
</div>
<div>
<h4 class="alert-title">{% block title %}{% trans title %}{% endblock %}</h4>
{% block content %}
<div class="text-secondary">{% trans message %}</div>
{% endblock content %}
</div>
</div>
</div>

View File

@@ -0,0 +1,4 @@
{# Display top or bottom banner content #}
<div class="text-center mx-3 m-2">
{{ content|safe }}
</div>

View File

@@ -12,7 +12,9 @@
{% for heading, fields in filter_form.fieldsets %}
<div class="col col-12">
{% if heading %}
<h6>{{ heading }}</h6>
<div class="hr-text">
<span>{{ heading }}</span>
</div>
{% endif %}
{% for name in fields %}
{% with field=filter_form|get_item:name %}
@@ -20,9 +22,6 @@
{% endwith %}
{% endfor %}
</div>
{% if not forloop.last %}
<hr class="card-divider mt-0" />
{% endif %}
{% empty %}
{# List all non-customfield filters as declared in the form class #}
{% for field in filter_form.visible_fields %}
@@ -35,21 +34,23 @@
{% endfor %}
{% if filter_form.custom_fields %}
{# List all custom field filters #}
<hr class="card-divider mt-0" />
{% for name in filter_form.custom_fields %}
<div class="col col-12">
<div class="col col-12">
<div class="hr-text">
<span>{% trans "Custom Fields" %}</span>
</div>
{% for name in filter_form.custom_fields %}
{% with field=filter_form|get_item:name %}
{% render_field field %}
{% endwith %}
</div>
{% endfor %}
{% endfor %}
</div>
{% endif %}
</div>
<div class="card-footer text-end noprint border-0">
<button type="button" class="btn btn-sm btn-outline-danger m-1" data-reset-select>
<div class="card-footer text-end d-print-none border-0">
<button type="button" class="btn btn-outline-danger m-1" data-reset-select>
<i class="mdi mdi-backspace"></i> {% trans "Reset" %}
</button>
<button type="submit" class="btn btn-sm btn-primary m-1">
<button type="submit" class="btn btn-primary m-1">
<i class="mdi mdi-magnify"></i> {% trans "Search" %}
</button>
</div>

View File

@@ -1,39 +1,17 @@
{% load helpers %}
<div id="django-messages" class="toast-container">
<div id="django-messages" class="toast-container position-fixed bottom-0 end-0 p-3">
{# Non-Field Form Errors #}
{% if form and form.non_field_errors %}
{% for error in form.non_field_errors.get_json_data %}
<div class="django-message toast align-items-center border-0 bg-danger" data-django-type="non-field-error" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="10000">
<div class="d-flex">
<div class="toast-body">
<i class="mdi mdi-{{ "danger"|icon_from_status }} me-1"></i>
{{ error.message }}
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
{% include 'inc/toast.html' with status="danger" title="Error" message=error.message %}
{% endfor %}
{% endif %}
{# Django Messages #}
{% if messages %}
{% for message in messages %}
{% with message.level_tag|status_from_tag as status %}
{% with status|icon_from_status as icon %}
<div class="django-message toast align-items-center border-0 bg-{{ status }}" data-django-type="message" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="10000">
<div class="d-flex">
<div class="toast-body">
<i class="mdi mdi-{{ icon }} me-1"></i>
{{ message }}
</div>
<button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
{% endwith %}
{% endwith %}
{% endfor %}
{% endif %}
{% for message in messages %}
{% include 'inc/toast.html' with status=message.level_tag|status_from_tag %}
{% endfor %}
</div>

View File

@@ -1,12 +1,16 @@
{% load buttons %}
{% load i18n %}
<div class="alert alert-warning text-end" role="alert">
<div class="float-start">
<i class="mdi mdi-alert"></i>
{% blocktrans trimmed with model=model|meta:"verbose_name" prerequisite_model=prerequisite_model|meta:"verbose_name" %}
Before you can add a {{ model }} you must first create a <strong>{{ prerequisite_model }}</strong>.
{% endblocktrans %}
<div class="alert alert-warning" role="alert">
<div class="d-flex justify-content-between">
<div>
<i class="mdi mdi-alert p-2"></i>
{% blocktrans trimmed with model=model|meta:"verbose_name" prerequisite_model=prerequisite_model|meta:"verbose_name" %}
Before you can add a {{ model }} you must first create a <strong>{{ prerequisite_model }}</strong>.
{% endblocktrans %}
</div>
<div>
{% add_button prerequisite_model %}
</div>
</div>
{% add_button prerequisite_model %}
</div>

View File

@@ -1,55 +1,117 @@
{% load helpers %}
{% load i18n %}
<div class="row">
<div class="col col-md-6 mb-0">
{# Page number carousel #}
{% if page %}
<div class="d-flex justify-content-between align-items-center border-top p-2">
{# Pages carousel #}
{% if paginator.num_pages > 1 %}
<div class="btn-group btn-group-sm mb-3" role="group" aria-label="Pages">
{% if page.has_previous %}
<a href="{% querystring request page=page.previous_page_number %}" class="btn btn-outline-secondary">
<i class="mdi mdi-chevron-double-left"></i>
</a>
{% endif %}
{% for p in page.smart_pages %}
{% if p %}
<a href="{% querystring request page=p %}" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}">
{{ p }}
</a>
{% else %}
<button type="button" class="btn btn-outline-secondary" disabled>
<span>&hellip;</span>
</button>
<nav aria-label="{% trans "Page selection" %}">
<ul class="pagination mb-0">
{# Previous page #}
{% if page.has_previous %}
<li class="page-item">
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.previous_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
<i class="mdi mdi-chevron-left"></i>
</a>
{% else %}
<a href="{% querystring request page=page.previous_page_number %}" class="page-link">
<i class="mdi mdi-chevron-left"></i>
</a>
{% endif %}
</li>
{% endif %}
{% endfor %}
{% if page.has_next %}
<a href="{% querystring request page=page.next_page_number %}" class="btn btn-outline-secondary">
<i class="mdi mdi-chevron-double-right"></i>
</a>
{% endif %}
</div>
{% endif %}
</div>
<div class="col col-md-6 mb-0 text-end">
{# Per-page count selector #}
{% if page %}
<div class="dropdown dropup">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
{% trans "Per Page" %}
</button>
<ul class="dropdown-menu">
{% for n in page.paginator.get_page_lengths %}
<li>
<a href="{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
{# /Previous page #}
{# Page numbers #}
{% for p in page.smart_pages %}
<li class="page-item{% if page.number == p %} active" aria-current="page{% endif %}">
{% if p and htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=p %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
{{ p }}
</a>
{% elif p %}
<a href="{% querystring request page=p %}" class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}">
{{ p }}
</a>
{% else %}
<span class="page-link" disabled>&hellip;</span>
{% endif %}
</li>
{% endfor %}
{# /Page numbers #}
{# Next page #}
{% if page.has_next %}
<li class="page-item">
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.next_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="page-link"
>
<i class="mdi mdi-chevron-right"></i>
</a>
{% else %}
<a href="{% querystring request page=page.next_page_number %}" class="page-link">
<i class="mdi mdi-chevron-right"></i>
</a>
{% endif %}
</li>
{% endif %}
{# /Next page #}
</ul>
</div>
<small class="text-end text-muted">
{% blocktrans trimmed with start=page.start_index end=page.end_index total=page.paginator.count %}
Showing {{ start }}-{{ end }} of {{ total }}
{% endblocktrans %}
</small>
</nav>
{% endif %}
{# /Pages carousel #}
{# Showing #}
<small class="text-end text-muted">
{% blocktrans trimmed with start=page.start_index end=page.end_index total=page.paginator.count %}
Showing {{ start }}-{{ end }} of {{ total }}
{% endblocktrans %}
</small>
{# /Showing #}
{# Pagination options #}
<nav class="text-end" aria-label="{% trans "Pagination options" %}">
{% if page %}
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
{% trans "Per Page" %}
</button>
<div class="dropdown-menu">
{% for n in page.paginator.get_page_lengths %}
{% if htmx %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request per_page=n %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="dropdown-item"
>{{ n }}</a>
{% else %}
<a href="{% querystring request per_page=n %}" class="dropdown-item">{{ n }}</a>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
</nav>
{# /Pagination options #}
</div>
</div>
{% endif %}

View File

@@ -1,75 +0,0 @@
{% load helpers %}
{% load i18n %}
<div class="row">
<div class="col col-md-6 mb-0">
{# Page number carousel #}
{% if paginator.num_pages > 1 %}
<div class="btn-group btn-group-sm" role="group" aria-label="Pages">
{% if page.has_previous %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.previous_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="btn btn-outline-secondary"
>
<i class="mdi mdi-chevron-double-left"></i>
</a>
{% endif %}
{% for p in page.smart_pages %}
{% if p %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=p %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="btn btn-outline-secondary{% if page.number == p %} active{% endif %}"
>
{{ p }}
</a>
{% else %}
<button type="button" class="btn btn-outline-secondary" disabled>
<span>&hellip;</span>
</button>
{% endif %}
{% endfor %}
{% if page.has_next %}
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request page=page.next_page_number %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="btn btn-outline-secondary"
>
<i class="mdi mdi-chevron-double-right"></i>
</a>
{% endif %}
</div>
{% endif %}
</div>
<div class="col col-md-6 mb-0 text-end">
{# Per-page count selector #}
{% if page %}
<div class="dropdown dropup">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
{% trans "Per Page" %}
</button>
<ul class="dropdown-menu">
{% for n in page.paginator.get_page_lengths %}
<li>
<a href="#"
hx-get="{{ table.htmx_url }}{% querystring request per_page=n %}"
hx-target="closest .htmx-container"
{% if not table.embedded %}hx-push-url="true"{% endif %}
class="dropdown-item"
>{{ n }}</a>
</li>
{% endfor %}
</ul>
</div>
<small class="text-end text-muted">
{% blocktrans trimmed with start=page.start_index end=page.end_index total=page.paginator.count %}
Showing {{ start }}-{{ end }} of {{ total }}
{% endblocktrans %}
</small>
{% endif %}
</div>
</div>

View File

@@ -5,7 +5,7 @@
{% if heading %}
<h5 class="card-header{% if panel_class %} text-{{ panel_class }}{% endif %}">{{ heading }}</h5>
{% endif %}
<div class="card-body table-responsive">
<div class="table-responsive">
{% if table.rows %}
{% render_table table 'inc/table.html' %}
{% else %}

View File

@@ -5,32 +5,30 @@
{% if custom_fields %}
<div class="card">
<h5 class="card-header">{% trans "Custom Fields" %}</h5>
<div class="card-body">
{% for group_name, fields in custom_fields.items %}
{% if group_name %}
<h6>{{ group_name }}</h6>
{% endif %}
<table class="table table-hover attr-table">
{% for field, value in fields.items %}
<tr>
<th scope="row">{{ field }}
{% if field.description %}
<i
class="mdi mdi-information text-primary"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="{{ field.description|escape }}"
></i>
{% endif %}
</th>
<td>
{% customfield_value field value %}
</td>
</tr>
{% endfor %}
</table>
{% endfor %}
</div>
{% for group_name, fields in custom_fields.items %}
{% if group_name %}
<h6>{{ group_name }}</h6>
{% endif %}
<table class="table table-hover attr-table">
{% for field, value in fields.items %}
<tr>
<th scope="row">{{ field }}
{% if field.description %}
<i
class="mdi mdi-information text-primary"
data-bs-toggle="tooltip"
data-bs-placement="right"
title="{{ field.description|escape }}"
></i>
{% endif %}
</th>
<td>
{% customfield_value field value %}
</td>
</tr>
{% endfor %}
</table>
{% endfor %}
</div>
{% endif %}
{% endwith %}

View File

@@ -5,8 +5,8 @@
<h5 class="card-header">{% trans "Images" %}</h5>
{% htmx_table 'extras:imageattachment_list' content_type_id=object|content_type_id object_id=object.pk %}
{% if perms.extras.add_imageattachment %}
<div class="card-footer text-end noprint">
<a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|content_type_id }}&object_id={{ object.pk }}" class="btn btn-primary btn-sm">
<div class="card-footer text-end d-print-none">
<a href="{% url 'extras:imageattachment_add' %}?content_type={{ object|content_type_id }}&object_id={{ object.pk }}" class="btn btn-primary">
<i class="mdi mdi-plus-thick" aria-hidden="true"></i> {% trans "Attach an image" %}
</a>
</div>

View File

@@ -10,9 +10,9 @@
{{ qs.model|meta:"verbose_name_plural"|bettertitle }}
{% with count=qs.count %}
{% if count %}
<span class="badge bg-primary rounded-pill">{{ count }}</span>
<span class="badge text-bg-primary rounded-pill">{{ count }}</span>
{% else %}
<span class="badge bg-light rounded-pill">&mdash;</span>
<span class="badge text-bg-light rounded-pill">&mdash;</span>
{% endif %}
{% endwith %}
</a>

View File

@@ -1,67 +0,0 @@
{% load i18n %}
{% if request.user.is_authenticated %}
<div class="dropdown profile-button">
<button type="button" aria-expanded="false" data-bs-toggle="dropdown" class="btn btn-outline-secondary dropdown-toggle w-100">
<i class="mdi mdi-account"></i>
<span id="navbar_user">{{ request.user|truncatechars:"30" }}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button type="button" class="dropdown-item color-mode-toggle">
<i class="color-mode-icon mdi mdi-lightbulb"></i>
<span class="color-mode-text">{% trans "Dark Mode" %}</span>
</button>
</li>
<li>
{% if request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> {% trans "Admin" %}
</a>
{% endif %}
</li>
<li>
<a class="dropdown-item" href="{% url 'account:profile' %}">
<i class="mdi mdi-account"></i> {% trans "Profile" %}
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'account:bookmarks' %}">
<i class="mdi mdi-bookmark"></i> {% trans "Bookmarks" %}
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'account:preferences' %}">
<i class="mdi mdi-wrench"></i> {% trans "Preferences" %}
</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'account:usertoken_list' %}">
<i class="mdi mdi-key"></i> {% trans "API Tokens" %}
</a>
</li>
<li><hr class="dropdown-divider" /></li>
<li>
<a class="dropdown-item" href="{% url 'logout' %}">
<i class="mdi mdi-logout-variant"></i> {% trans "Log Out" %}
</a>
</li>
</ul>
</div>
{% else %}
<div class="btn-group">
<a class="btn btn-primary ws-nowrap" type="button" href="{% url 'login' %}?next={{ request.path }}">
<i class="mdi mdi-login-variant"></i> {% trans "Log In" %}
</a>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<button class="dropdown-item color-mode-toggle">
<i class="color-mode-icon mdi mdi-lightbulb"></i>
<span class="color-mode-text">{% trans "Dark Mode" %}</span>
</button>
</li>
</ul>
</div>
{% endif %}

View File

@@ -1,6 +0,0 @@
<form class="input-group" action="{% url 'search' %}" method="get">
<input name="q" type="text" aria-label="Search" placeholder="Search" class="form-control" />
<button class="btn btn-primary" type="submit">
<i class="mdi mdi-magnify"></i>
</button>
</form>

View File

@@ -4,11 +4,16 @@
{% if object.data_file and object.data_file.last_updated > object.data_synced %}
<div class="alert alert-warning" role="alert">
<i class="mdi mdi-alert"></i> {% trans "Data is out of sync with upstream file" %} (<a href="{{ object.data_file.get_absolute_url }}">{{ object.data_file }}</a>).
{% if request.user|can_sync:object %}
<div class="float-end">
{% sync_button object %}
<div class="d-flex justify-content-between">
<div>
<i class="mdi mdi-alert"></i>
{% trans "Data is out of sync with upstream file" %} (<a href="{{ object.data_file.get_absolute_url }}">{{ object.data_file }}</a>).
</div>
{% endif %}
{% if request.user|can_sync:object %}
<div>
{% sync_button object %}
</div>
{% endif %}
</div>
</div>
{% endif %}

View File

@@ -2,19 +2,19 @@
{% load i18n %}
<div class="row mb-3">
<div class="col-auto table-controls noprint">
<div class="input-group input-group-sm me-2 quicksearch hide-last-child">
<div class="col-auto d-print-none">
<div class="input-group me-2 quicksearch hide-last-child">
<input type="search" results="5" name="q" id="quicksearch" class="form-control" placeholder="Quick search"
hx-get="{{ request.full_path }}" hx-target="#object_list" hx-trigger="keyup changed delay:500ms, search" />
<button class="btn bg-transparent" type="button" id="quicksearch_clear"><i class="mdi mdi-close-circle"></i></button>
</div>
{% block extra_table_controls %}{% endblock %}
</div>
<div class="col-auto ms-auto table-controls noprint">
<div class="col-auto ms-auto d-print-none">
{% if request.user.is_authenticated and table_modal %}
<div class="table-configure input-group input-group-sm">
<div class="table-configure input-group">
<button type="button" data-bs-toggle="modal" title="{% trans "Configure Table" %}" data-bs-target="#{{ table_modal }}"
class="btn btn-sm btn-outline-dark">
class="btn">
<i class="mdi mdi-cog"></i> {% trans "Configure Table" %}
</button>
</div>

View File

@@ -0,0 +1,12 @@
{% load helpers %}
<div class="toast shadow-sm" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="10000">
<div class="toast-header text-bg-{{ status }}">
<i class="mdi mdi-{{ status|icon_from_status }} me-1"></i>
{{ title }}
<button type="button" class="btn-close me-0 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
<div class="toast-body">
{{ message }}
</div>
</div>

View File

@@ -0,0 +1,41 @@
{% load i18n %}
{% if request.user.is_authenticated %}
<div class="nav-item dropdown">
<a href="#" class="nav-link d-flex lh-1 text-reset p-0" data-bs-toggle="dropdown" aria-label="Open user menu">
<div class="d-xl-block ps-2">
<div>{{ request.user }}</div>
<div class="mt-1 small text-secondary">{% if request.user.is_staff %}Staff{% else %}User{% endif %}</div>
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
{% if request.user.is_staff %}
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="mdi mdi-cog"></i> {% trans "Admin" %}
</a>
{% endif %}
<a href="{% url 'account:profile' %}" class="dropdown-item">
<i class="mdi mdi-account"></i> {% trans "Profile" %}
</a>
<a href="{% url 'account:bookmarks' %}" class="dropdown-item">
<i class="mdi mdi-bookmark"></i> {% trans "Bookmarks" %}
</a>
<a href="{% url 'account:preferences' %}" class="dropdown-item">
<i class="mdi mdi-wrench"></i> {% trans "Preferences" %}
</a>
<a href="{% url 'account:usertoken_list' %}" class="dropdown-item">
<i class="mdi mdi-key"></i> {% trans "API Tokens" %}
</a>
<div class="dropdown-divider"></div>
<a href="{% url 'logout' %}" class="dropdown-item">
<i class="mdi mdi-logout-variant"></i> {% trans "Log Out" %}
</a>
</div>
</div>
{% else %}
<div class="btn-group">
<a class="btn btn-primary" type="button" href="{% url 'login' %}?next={{ request.path }}">
<i class="mdi mdi-login-variant"></i> {% trans "Log In" %}
</a>
</div>
{% endif %}