diff --git a/debug_toolbar/panels/__init__.py b/debug_toolbar/panels/__init__.py index a53ba6652..dbd0e190e 100644 --- a/debug_toolbar/panels/__init__.py +++ b/debug_toolbar/panels/__init__.py @@ -3,7 +3,7 @@ from django.utils.functional import classproperty from debug_toolbar import settings as dt_settings -from debug_toolbar.utils import get_name_from_obj +from debug_toolbar.utils import ToolbarHealthLevel, get_name_from_obj class Panel: @@ -129,6 +129,19 @@ def scripts(self): """ return [] + @property + def health_level(self): + """ + Returns the health level of the panel as a `ToolbarHealthLevel` enum value. + + This property is used by the toolbar to determine the overall health status of each panel. + The default implementation returns `ToolbarHealthLevel.NONE`, indicating no issues. + + Subclasses should override this property to provide custom health logic, returning + `ToolbarHealthLevel.WARNING` or `ToolbarHealthLevel.ERROR` as appropriate based on panel-specific conditions. + """ + return ToolbarHealthLevel.NONE + # Panel early initialization @classmethod diff --git a/debug_toolbar/panels/alerts.py b/debug_toolbar/panels/alerts.py index 030ca1ee1..56aecf823 100644 --- a/debug_toolbar/panels/alerts.py +++ b/debug_toolbar/panels/alerts.py @@ -3,7 +3,7 @@ from django.utils.translation import gettext_lazy as _ from debug_toolbar.panels import Panel -from debug_toolbar.utils import is_processable_html_response +from debug_toolbar.utils import ToolbarHealthLevel, is_processable_html_response class FormParser(HTMLParser): @@ -92,6 +92,16 @@ def nav_subtitle(self): else: return "" + @property + def health_level(self): + """ + Return the health level of the panel based on the alerts. + """ + if not self.alerts: + return ToolbarHealthLevel.NONE + + return ToolbarHealthLevel.CRITICAL + def add_alert(self, alert): self.alerts.append(alert) diff --git a/debug_toolbar/panels/sql/panel.py b/debug_toolbar/panels/sql/panel.py index 45143ef94..7ad781b5a 100644 --- a/debug_toolbar/panels/sql/panel.py +++ b/debug_toolbar/panels/sql/panel.py @@ -19,7 +19,7 @@ is_select_query, reformat_sql, ) -from debug_toolbar.utils import render_stacktrace +from debug_toolbar.utils import ToolbarHealthLevel, render_stacktrace def get_isolation_level_display(vendor, level): @@ -186,6 +186,18 @@ def title(self): count, ) % {"count": count} + @property + def health_level(self): + """ """ + stats = self.get_stats() + slow_queries = len([1 for q in stats.get("queries", []) if q.get("is_slow")]) + if slow_queries > 10: + return ToolbarHealthLevel.CRITICAL + elif slow_queries > 0: + return ToolbarHealthLevel.WARNING + + return super().health_level + template = "debug_toolbar/panels/sql.html" @classmethod diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index 43b432069..41dfb175f 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -34,6 +34,13 @@ --djdt-button-border-color: var(--djdt-table-border-color); --djdt-pre-border-color: var(--djdt-table-border-color); --djdt-raw-border-color: var(--djdt-table-border-color); + + --djdt-health-bg-1: #4b3f1b; + --djdt-health-color-1: #ffe761; + --djdt-health-bc-1: #ffcc00; + --djdt-health-bg-2: #5a2327; + --djdt-health-color-2: #ffb3b3; + --djdt-health-bc-2: #ff0000; } #djDebug[data-theme="dark"] { @@ -56,6 +63,13 @@ --djdt-button-border-color: var(--djdt-table-border-color); --djdt-pre-border-color: var(--djdt-table-border-color); --djdt-raw-border-color: var(--djdt-table-border-color); + + --djdt-health-bg-1: #4b3f1b; + --djdt-health-color-1: #ffe761; + --djdt-health-bc-1: #ffcc00; + --djdt-health-bg-2: #5a2327; + --djdt-health-color-2: #ffb3b3; + --djdt-health-bc-2: #ff0000; } /* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */ @@ -377,6 +391,25 @@ animation: spin 2s linear infinite; } +/* Panel and Toolbar hidden button health states */ +#djDebug .djdt-health-1 { + background: var(--djdt-health-bg-1) !important; + color: var(--djdt-health-color-1) !important; +} + +#djDebug .djdt-health-2 { + background: var(--djdt-health-bg-2) !important; + color: var(--djdt-health-color-2) !important; +} + +#djDebug .djdt-toolbarhandle-health-1 { + border-color: var(--djdt-health-bc-1) !important; +} + +#djDebug .djdt-toolbarhandle-health-2 { + border-color: var(--djdt-health-bc-2) !important; +} + @keyframes spin { 0% { transform: rotate(0deg); diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index b7562ecba..110d12589 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -31,7 +31,7 @@
-
+
DJDT
diff --git a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html index bc6f03ad9..68232ec09 100644 --- a/debug_toolbar/templates/debug_toolbar/includes/panel_button.html +++ b/debug_toolbar/templates/debug_toolbar/includes/panel_button.html @@ -1,6 +1,6 @@ {% load i18n %} -
  • +
  • {% if panel.has_content and panel.enabled %} diff --git a/debug_toolbar/toolbar.py b/debug_toolbar/toolbar.py index 6ebc74234..dfcb36aa7 100644 --- a/debug_toolbar/toolbar.py +++ b/debug_toolbar/toolbar.py @@ -72,6 +72,14 @@ def csp_nonce(self): """ return getattr(self.request, "csp_nonce", None) + @property + def health_level(self): + """ + Return the maximum health level across all panels. + This is used to color the toolbar hidden button. + """ + return max(panel.health_level for panel in self.panels) + def get_panel_by_id(self, panel_id): """ Get the panel with the given id, which is the class name by default. diff --git a/debug_toolbar/utils.py b/debug_toolbar/utils.py index f4b3eac38..4e3844a1c 100644 --- a/debug_toolbar/utils.py +++ b/debug_toolbar/utils.py @@ -6,6 +6,7 @@ import sys import warnings from collections.abc import Sequence +from enum import IntEnum from pprint import PrettyPrinter, pformat from typing import Any @@ -401,3 +402,9 @@ def is_processable_html_response(response): and content_encoding == "" and content_type in _HTML_TYPES ) + + +class ToolbarHealthLevel(IntEnum): + NONE = 0 + WARNING = 1 + CRITICAL = 2