diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index b0c799d88..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -module.exports = { - root: true, - "env": { - "browser": true, - "es6": true, - node: true, - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 6, - "sourceType": "module" - }, - "rules": { - "curly": ["error", "all"], - "dot-notation": "error", - "eqeqeq": "error", - "no-eval": "error", - "no-var": "error", - "prefer-const": "error", - "semi": "error" - } -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..be006de9a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Keep GitHub Actions up to date with GitHub's Dependabot... +# https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + groups: + github-actions: + patterns: + - "*" # Group all Actions updates into a single larger pull request + schedule: + interval: weekly diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..a0722f0ac --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,33 @@ +# .github/workflows/coverage.yml +name: Post coverage comment + +on: + workflow_run: + workflows: ["Test"] + types: + - completed + +jobs: + test: + name: Run tests & display coverage + runs-on: ubuntu-latest + if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' + permissions: + # Gives the action the necessary permissions for publishing new + # comments in pull requests. + pull-requests: write + # Gives the action the necessary permissions for editing existing + # comments (to avoid publishing multiple comments in the same PR) + contents: write + # Gives the action the necessary permissions for looking up the + # workflow that launched this workflow, and download the related + # artifact that contains the comment to be published + actions: read + steps: + # DO NOT run actions/checkout here, for security reasons + # For details, refer to https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ + - name: Post comment + uses: py-cov-action/python-coverage-comment-action@v3 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_PR_RUN_ID: ${{ github.event.workflow_run.id }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2059d37f8..b57181444 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,7 +16,7 @@ jobs: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb28e217e..cd5d8dd8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -44,7 +44,7 @@ jobs: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -66,11 +66,6 @@ jobs: DB_HOST: 127.0.0.1 DB_PORT: 3306 - - name: Upload coverage data - uses: actions/upload-artifact@v3 - with: - name: coverage-data - path: ".coverage.*" postgres: runs-on: ubuntu-latest @@ -108,7 +103,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -119,7 +114,7 @@ jobs: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -144,12 +139,6 @@ jobs: DB_HOST: localhost DB_PORT: 5432 - - name: Upload coverage data - uses: actions/upload-artifact@v3 - with: - name: coverage-data - path: ".coverage.*" - sqlite: runs-on: ubuntu-latest strategy: @@ -162,7 +151,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -173,7 +162,7 @@ jobs: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: @@ -192,43 +181,6 @@ jobs: DB_BACKEND: sqlite3 DB_NAME: ":memory:" - - name: Upload coverage data - uses: actions/upload-artifact@v3 - with: - name: coverage-data - path: ".coverage.*" - - coverage: - name: Check coverage. - runs-on: "ubuntu-latest" - needs: [sqlite, mysql, postgres] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 - with: - # Use latest, so it understands all syntax. - python-version: "3.11" - - - run: python -m pip install --upgrade coverage[toml] - - - name: Download coverage data. - uses: actions/download-artifact@v3 - with: - name: coverage-data - - - name: Combine coverage & check percentage - run: | - python -m coverage combine - python -m coverage html - python -m coverage report - - - name: Upload HTML report if check failed. - uses: actions/upload-artifact@v3 - with: - name: html-report - path: htmlcov - if: ${{ failure() }} - lint: runs-on: ubuntu-latest strategy: @@ -238,7 +190,7 @@ jobs: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.8 @@ -248,7 +200,7 @@ jobs: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT - name: Cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ steps.pip-cache.outputs.dir }} key: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c2f93ac73..1eb0a7df1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-toml - id: check-yaml @@ -14,10 +14,10 @@ repos: hooks: - id: doc8 - repo: https://github.com/adamchainz/django-upgrade - rev: 1.15.0 + rev: 1.16.0 hooks: - id: django-upgrade - args: [--target-version, "3.2"] + args: [--target-version, "4.2"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: @@ -32,24 +32,28 @@ repos: args: - --trailing-comma=es5 - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.56.0 + rev: v9.0.0 hooks: - id: eslint + additional_dependencies: + - "eslint@v9.0.0-beta.1" + - "@eslint/js@v9.0.0-beta.1" + - "globals" files: \.js?$ types: [file] args: - --fix - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.1.11' + rev: 'v0.3.7' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.5.3 + rev: 1.7.0 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.15 + rev: v0.16 hooks: - id: validate-pyproject diff --git a/Makefile b/Makefile index 1600496e5..24b59ab95 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,9 @@ example: --noinput --username="$(USER)" --email="$(USER)@mailinator.com" python example/manage.py runserver +example_test: + python example/manage.py test example + test: DJANGO_SETTINGS_MODULE=tests.settings \ python -m django test $${TEST_ARGS:-tests} diff --git a/README.rst b/README.rst index 0eaaa6bd3..24dba95a3 100644 --- a/README.rst +++ b/README.rst @@ -44,8 +44,8 @@ Here's a screenshot of the toolbar in action: In addition to the built-in panels, a number of third-party panels are contributed by the community. -The current stable version of the Debug Toolbar is 4.1.0. It works on -Django ≥ 3.2.4. +The current stable version of the Debug Toolbar is 4.4.0. It works on +Django ≥ 4.2.0. The Debug Toolbar does not currently support `Django's asynchronous views `_. diff --git a/debug_toolbar/__init__.py b/debug_toolbar/__init__.py index 9a8c2b24f..ecf3e559a 100644 --- a/debug_toolbar/__init__.py +++ b/debug_toolbar/__init__.py @@ -4,7 +4,7 @@ # Do not use pkg_resources to find the version but set it here directly! # see issue #1446 -VERSION = "4.3.0" +VERSION = "4.4.0" # Code that discovers files or modules in INSTALLED_APPS imports this module. urls = "debug_toolbar.urls", APP_NAME diff --git a/debug_toolbar/apps.py b/debug_toolbar/apps.py index 0a10a4b08..a2e977d84 100644 --- a/debug_toolbar/apps.py +++ b/debug_toolbar/apps.py @@ -3,7 +3,7 @@ from django.apps import AppConfig from django.conf import settings -from django.core.checks import Warning, register +from django.core.checks import Error, Warning, register from django.middleware.gzip import GZipMiddleware from django.utils.module_loading import import_string from django.utils.translation import gettext_lazy as _ @@ -177,7 +177,7 @@ def check_panels(app_configs, **kwargs): return errors -@register() +@register def js_mimetype_check(app_configs, **kwargs): """ Check that JavaScript files are resolving to the correct content type. @@ -206,3 +206,40 @@ def js_mimetype_check(app_configs, **kwargs): ) ] return [] + + +@register +def debug_toolbar_installed_when_running_tests_check(app_configs, **kwargs): + """ + Check that the toolbar is not being used when tests are running + """ + if not settings.DEBUG and dt_settings.get_config()["IS_RUNNING_TESTS"]: + return [ + Error( + "The Django Debug Toolbar can't be used with tests", + hint="Django changes the DEBUG setting to False when running " + "tests. By default the Django Debug Toolbar is installed because " + "DEBUG is set to True. For most cases, you need to avoid installing " + "the toolbar when running tests. If you feel this check is in error, " + "you can set `DEBUG_TOOLBAR_CONFIG['IS_RUNNING_TESTS'] = False` to " + "bypass this check.", + id="debug_toolbar.E001", + ) + ] + else: + return [] + + +@register +def check_settings(app_configs, **kwargs): + errors = [] + USER_CONFIG = getattr(settings, "DEBUG_TOOLBAR_CONFIG", {}) + if "OBSERVE_REQUEST_CALLBACK" in USER_CONFIG: + errors.append( + Warning( + "The deprecated OBSERVE_REQUEST_CALLBACK setting is present in DEBUG_TOOLBAR_CONFIG.", + hint="Use the UPDATE_ON_FETCH and/or SHOW_TOOLBAR_CALLBACK settings instead.", + id="debug_toolbar.W008", + ) + ) + return errors diff --git a/debug_toolbar/middleware.py b/debug_toolbar/middleware.py index b5e5d0827..38cf92884 100644 --- a/debug_toolbar/middleware.py +++ b/debug_toolbar/middleware.py @@ -3,6 +3,7 @@ """ import re +import socket from functools import lru_cache from django.conf import settings @@ -19,7 +20,22 @@ def show_toolbar(request): """ Default function to determine whether to show the toolbar on a given page. """ - return settings.DEBUG and request.META.get("REMOTE_ADDR") in settings.INTERNAL_IPS + internal_ips = settings.INTERNAL_IPS.copy() + + try: + # This is a hack for docker installations. It attempts to look + # up the IP address of the docker host. + # This is not guaranteed to work. + docker_ip = ( + # Convert the last segment of the IP address to be .1 + ".".join(socket.gethostbyname("host.docker.internal").rsplit(".")[:-1]) + + ".1" + ) + internal_ips.append(docker_ip) + except socket.gaierror: + # It's fine if the lookup errored since they may not be using docker + pass + return settings.DEBUG and request.META.get("REMOTE_ADDR") in internal_ips @lru_cache(maxsize=None) diff --git a/debug_toolbar/panels/profiling.py b/debug_toolbar/panels/profiling.py index 2b7742400..64224a2db 100644 --- a/debug_toolbar/panels/profiling.py +++ b/debug_toolbar/panels/profiling.py @@ -86,12 +86,10 @@ def func_std_string(self): # match what old profile produced ) def subfuncs(self): - i = 0 h, s, v = self.hsv count = len(self.statobj.all_callees[self.func]) - for func, stats in self.statobj.all_callees[self.func].items(): - i += 1 - h1 = h + (i / count) / (self.depth + 1) + for i, (func, stats) in enumerate(self.statobj.all_callees[self.func].items()): + h1 = h + ((i + 1) / count) / (self.depth + 1) s1 = 0 if stats[3] == 0 else s * (stats[3] / self.stats[3]) yield FunctionCall( self.statobj, diff --git a/debug_toolbar/panels/staticfiles.py b/debug_toolbar/panels/staticfiles.py index 5f9efb5c3..2eed2efa0 100644 --- a/debug_toolbar/panels/staticfiles.py +++ b/debug_toolbar/panels/staticfiles.py @@ -4,7 +4,6 @@ from django.conf import settings from django.contrib.staticfiles import finders, storage -from django.core.checks import Warning from django.utils.functional import LazyObject from django.utils.translation import gettext_lazy as _, ngettext @@ -178,27 +177,3 @@ def get_staticfiles_apps(self): if app not in apps: apps.append(app) return apps - - @classmethod - def run_checks(cls): - """ - Check that the integration is configured correctly for the panel. - - Specifically look for static files that haven't been collected yet. - - Return a list of :class: `django.core.checks.CheckMessage` instances. - """ - errors = [] - for finder in finders.get_finders(): - try: - for path, finder_storage in finder.list([]): - finder_storage.path(path) - except OSError: - errors.append( - Warning( - "debug_toolbar requires the STATICFILES_DIRS directories to exist.", - hint="Running manage.py collectstatic may help uncover the issue.", - id="debug_toolbar.staticfiles.W001", - ) - ) - return errors diff --git a/debug_toolbar/panels/templates/panel.py b/debug_toolbar/panels/templates/panel.py index f8c9242ca..c0c6246b2 100644 --- a/debug_toolbar/panels/templates/panel.py +++ b/debug_toolbar/panels/templates/panel.py @@ -154,9 +154,9 @@ def process_context_list(self, context_layers): # QuerySet would trigger the database: user can run the # query from SQL Panel elif isinstance(value, (QuerySet, RawQuerySet)): - temp_layer[ - key - ] = f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>" + temp_layer[key] = ( + f"<<{value.__class__.__name__.lower()} of {value.model._meta.label}>>" + ) else: token = allow_sql.set(False) # noqa: FBT003 try: diff --git a/debug_toolbar/settings.py b/debug_toolbar/settings.py index 1df24527d..ca7036c34 100644 --- a/debug_toolbar/settings.py +++ b/debug_toolbar/settings.py @@ -1,3 +1,4 @@ +import sys import warnings from functools import lru_cache @@ -42,7 +43,9 @@ "SQL_WARNING_THRESHOLD": 500, # milliseconds "OBSERVE_REQUEST_CALLBACK": "debug_toolbar.toolbar.observe_request", "TOOLBAR_LANGUAGE": None, + "IS_RUNNING_TESTS": "test" in sys.argv, "UPDATE_ON_FETCH": False, + "DEFAULT_THEME": "auto", } diff --git a/debug_toolbar/static/debug_toolbar/css/toolbar.css b/debug_toolbar/static/debug_toolbar/css/toolbar.css index a35286a1f..170cc3d5f 100644 --- a/debug_toolbar/static/debug_toolbar/css/toolbar.css +++ b/debug_toolbar/static/debug_toolbar/css/toolbar.css @@ -1,5 +1,6 @@ /* Variable definitions */ -:root { +:root, +#djDebug[data-theme="light"] { /* Font families are the same as in Django admin/css/base.css */ --djdt-font-family-primary: "Segoe UI", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", @@ -9,12 +10,79 @@ "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + + color-scheme: light; + --djdt-font-color: black; + --djdt-background-color: white; + --djdt-panel-content-background-color: #eee; + --djdt-panel-content-table-background-color: var(--djdt-background-color); + --djdt-panel-title-background-color: #ffc; + --djdt-djdt-panel-content-table-strip-background-color: #f5f5f5; + --djdt--highlighted-background-color: lightgrey; + --djdt-toggle-template-background-color: #bbb; + + --djdt-sql-font-color: #333; + --djdt-pre-text-color: #555; + --djdt-path-and-locals: #777; + --djdt-stack-span-color: black; + --djdt-template-highlight-color: #333; + + --djdt-table-border-color: #ccc; + --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); +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + --djdt-font-color: #8393a7; + --djdt-background-color: #1e293bff; + --djdt-panel-content-background-color: #0f1729ff; + --djdt-panel-title-background-color: #242432; + --djdt-djdt-panel-content-table-strip-background-color: #324154ff; + --djdt--highlighted-background-color: #2c2a7dff; + --djdt-toggle-template-background-color: #282755; + + --djdt-sql-font-color: var(--djdt-font-color); + --djdt-pre-text-color: var(--djdt-font-color); + --djdt-path-and-locals: #65758cff; + --djdt-stack-span-color: #7c8fa4; + --djdt-template-highlight-color: var(--djdt-stack-span-color); + + --djdt-table-border-color: #324154ff; + --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); + } +} + +#djDebug[data-theme="dark"] { + color-scheme: dark; + --djdt-font-color: #8393a7; + --djdt-background-color: #1e293bff; + --djdt-panel-content-background-color: #0f1729ff; + --djdt-panel-title-background-color: #242432; + --djdt-djdt-panel-content-table-strip-background-color: #324154ff; + --djdt--highlighted-background-color: #2c2a7dff; + --djdt-toggle-template-background-color: #282755; + + --djdt-sql-font-color: var(--djdt-font-color); + --djdt-pre-text-color: var(--djdt-font-color); + --djdt-path-and-locals: #65758cff; + --djdt-stack-span-color: #7c8fa4; + --djdt-template-highlight-color: var(--djdt-stack-span-color); + + --djdt-table-border-color: #324154ff; + --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); } /* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */ #djDebug { - color: #000; - background: #fff; + color: var(--djdt-font-color); + background: var(--djdt-background-color); } #djDebug, #djDebug div, @@ -87,7 +155,7 @@ outline: 0; font-size: 12px; line-height: 1.5em; - color: #000; + color: var(--djdt-font-color); vertical-align: baseline; background-color: transparent; font-family: var(--djdt-font-family-primary); @@ -100,7 +168,7 @@ #djDebug button { background-color: #eee; background-image: linear-gradient(to bottom, #eee, #cccccc); - border: 1px solid #ccc; + border: 1px solid var(--djdt-button-border-color); border-bottom: 1px solid #bbb; border-radius: 3px; color: #333; @@ -268,10 +336,10 @@ #djDebug pre { white-space: pre-wrap; - color: #555; - border: 1px solid #ccc; + color: var(--djdt-pre-text-color); + border: 1px solid var(--djdt-pre-border-color); border-collapse: collapse; - background-color: #fff; + background-color: var(--djdt-background-color); padding: 2px 3px; margin-bottom: 3px; } @@ -283,7 +351,7 @@ right: 220px; bottom: 0; left: 0px; - background-color: #eee; + background-color: var(--djdt-panel-content-background-color); color: #666; z-index: 100000000; } @@ -294,7 +362,7 @@ #djDebug .djDebugPanelTitle { position: absolute; - background-color: #ffc; + background-color: var(--djdt-panel-title-background-color); color: #666; padding-left: 20px; top: 0; @@ -357,16 +425,16 @@ } #djDebug .djdt-panelContent table { - border: 1px solid #ccc; + border: 1px solid var(--djdt-table-border-color); border-collapse: collapse; width: 100%; - background-color: #fff; + background-color: var(--djdt-panel-content-table-background-color); display: table; margin-top: 0.8em; overflow: auto; } #djDebug .djdt-panelContent tbody > tr:nth-child(odd):not(.djdt-highlighted) { - background-color: #f5f5f5; + background-color: var(--djdt-panel-content-table-strip-background-color); } #djDebug .djdt-panelContent tbody td, #djDebug .djdt-panelContent tbody th { @@ -392,7 +460,7 @@ } #djDebug .djTemplateContext { - background-color: #fff; + background-color: var(--djdt-background-color); } #djDebug .djdt-panelContent .djDebugClose { @@ -433,7 +501,7 @@ #djDebug a.toggleTemplate { padding: 4px; - background-color: #bbb; + background-color: var(--djdt-toggle-template-background-color); border-radius: 3px; } @@ -445,11 +513,11 @@ } #djDebug .djDebugCollapsed { - color: #333; + color: var(--djdt-sql-font-color); } #djDebug .djDebugUncollapsed { - color: #333; + color: var(--djdt-sql-font-color); } #djDebug .djUnselected { @@ -483,66 +551,66 @@ } #djDebug .highlight { - color: #000; + color: var(--djdt-font-color); } #djDebug .highlight .err { - color: #000; + color: var(--djdt-font-color); } /* Error */ #djDebug .highlight .g { - color: #000; + color: var(--djdt-font-color); } /* Generic */ #djDebug .highlight .k { - color: #000; + color: var(--djdt-font-color); font-weight: bold; } /* Keyword */ #djDebug .highlight .o { - color: #000; + color: var(--djdt-font-color); } /* Operator */ #djDebug .highlight .n { - color: #000; + color: var(--djdt-font-color); } /* Name */ #djDebug .highlight .mi { - color: #000; + color: var(--djdt-font-color); font-weight: bold; } /* Literal.Number.Integer */ #djDebug .highlight .l { - color: #000; + color: var(--djdt-font-color); } /* Literal */ #djDebug .highlight .x { - color: #000; + color: var(--djdt-font-color); } /* Other */ #djDebug .highlight .p { - color: #000; + color: var(--djdt-font-color); } /* Punctuation */ #djDebug .highlight .m { - color: #000; + color: var(--djdt-font-color); font-weight: bold; } /* Literal.Number */ #djDebug .highlight .s { - color: #333; + color: var(--djdt-template-highlight-color); } /* Literal.String */ #djDebug .highlight .w { color: #888888; } /* Text.Whitespace */ #djDebug .highlight .il { - color: #000; + color: var(--djdt-font-color); font-weight: bold; } /* Literal.Number.Integer.Long */ #djDebug .highlight .na { - color: #333; + color: var(--djdt-template-highlight-color); } /* Name.Attribute */ #djDebug .highlight .nt { - color: #000; + color: var(--djdt-font-color); font-weight: bold; } /* Name.Tag */ #djDebug .highlight .nv { - color: #333; + color: var(--djdt-template-highlight-color); } /* Name.Variable */ #djDebug .highlight .s2 { - color: #333; + color: var(--djdt-template-highlight-color); } /* Literal.String.Double */ #djDebug .highlight .cp { - color: #333; + color: var(--djdt-template-highlight-color); } /* Comment.Preproc */ #djDebug svg.djDebugLineChart { @@ -595,13 +663,13 @@ } #djDebug .djdt-stack span { - color: #000; + color: var(--djdt-stack-span-color); font-weight: bold; } #djDebug .djdt-stack span.djdt-path, #djDebug .djdt-stack pre.djdt-locals, #djDebug .djdt-stack pre.djdt-locals span { - color: #777; + color: var(--djdt-path-and-locals); font-weight: normal; } #djDebug .djdt-stack span.djdt-code { @@ -612,7 +680,7 @@ } #djDebug .djdt-raw { background-color: #fff; - border: 1px solid #ccc; + border: 1px solid var(--djdt-raw-border-color); margin-top: 0.8em; padding: 5px; white-space: pre-wrap; @@ -631,7 +699,7 @@ max-height: 100%; } #djDebug .djdt-highlighted { - background-color: lightgrey; + background-color: var(--djdt--highlighted-background-color); } #djDebug tr.djdt-highlighted.djdt-profile-row { background-color: #ffc; @@ -654,3 +722,17 @@ .djdt-hidden { display: none; } + +#djDebug #djDebugToolbar a#djToggleThemeButton { + display: flex; + align-items: center; + cursor: pointer; +} +#djToggleThemeButton > svg { + margin-left: auto; +} +#djDebug[data-theme="light"] #djToggleThemeButton svg.theme-light, +#djDebug[data-theme="dark"] #djToggleThemeButton svg.theme-dark, +#djDebug[data-theme="auto"] #djToggleThemeButton svg.theme-auto { + display: block; +} diff --git a/debug_toolbar/static/debug_toolbar/js/toolbar.js b/debug_toolbar/static/debug_toolbar/js/toolbar.js index 199616336..067b5a312 100644 --- a/debug_toolbar/static/debug_toolbar/js/toolbar.js +++ b/debug_toolbar/static/debug_toolbar/js/toolbar.js @@ -1,4 +1,4 @@ -import { $$, ajax, replaceToolbarState, debounce } from "./utils.js"; +import { $$, ajax, debounce, replaceToolbarState } from "./utils.js"; function onKeyDown(event) { if (event.keyCode === 27) { @@ -213,6 +213,29 @@ const djdt = { if (djDebug.dataset.sidebarUrl !== undefined) { djdt.updateOnAjax(); } + + // Updates the theme using user settings + const userTheme = localStorage.getItem("djdt.user-theme"); + if (userTheme !== null) { + djDebug.setAttribute("data-theme", userTheme); + } + // Adds the listener to the Theme Toggle Button + $$.on(djDebug, "click", "#djToggleThemeButton", function () { + switch (djDebug.getAttribute("data-theme")) { + case "auto": + djDebug.setAttribute("data-theme", "light"); + localStorage.setItem("djdt.user-theme", "light"); + break; + case "light": + djDebug.setAttribute("data-theme", "dark"); + localStorage.setItem("djdt.user-theme", "dark"); + break; + default: /* dark is the default */ + djDebug.setAttribute("data-theme", "auto"); + localStorage.setItem("djdt.user-theme", "auto"); + break; + } + }); }, hidePanels() { const djDebug = getDebugElement(); @@ -276,7 +299,7 @@ const djdt = { storeId = encodeURIComponent(storeId); const dest = `${sidebarUrl}?store_id=${storeId}`; slowjax(dest).then(function (data) { - if (djdt.needUpdateOnFetch){ + if (djdt.needUpdateOnFetch) { replaceToolbarState(storeId, data); } }); diff --git a/debug_toolbar/static/debug_toolbar/js/utils.js b/debug_toolbar/static/debug_toolbar/js/utils.js index b4c7a4cb8..c37525f13 100644 --- a/debug_toolbar/static/debug_toolbar/js/utils.js +++ b/debug_toolbar/static/debug_toolbar/js/utils.js @@ -75,7 +75,11 @@ function ajax(url, init) { return fetch(url, init) .then(function (response) { if (response.ok) { - return response.json(); + return response.json().catch(function(error){ + return Promise.reject( + new Error("The response is a invalid Json object : " + error) + ); + }); } return Promise.reject( new Error(response.status + ": " + response.statusText) diff --git a/debug_toolbar/templates/debug_toolbar/base.html b/debug_toolbar/templates/debug_toolbar/base.html index 6f4967f21..4867a834e 100644 --- a/debug_toolbar/templates/debug_toolbar/base.html +++ b/debug_toolbar/templates/debug_toolbar/base.html @@ -16,10 +16,16 @@ data-sidebar-url="{{ history_url }}" {% endif %} data-default-show="{% if toolbar.config.SHOW_COLLAPSED %}false{% else %}true{% endif %}" - {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}"> + {{ toolbar.config.ROOT_TAG_EXTRA_ATTRS|safe }} data-update-on-fetch="{{ toolbar.config.UPDATE_ON_FETCH }}" + data-theme="{{ toolbar.config.DEFAULT_THEME }}">