From 6b19cbcb513abdc7efece72e37f0fbdc2b10472a Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Sat, 17 May 2025 20:18:21 +0200 Subject: [PATCH] Add configuration and scripts for linting, testing, and dependency management - Introduced .pre-commit-config.yaml for pre-commit hooks using uv-pre-commit. - Created lint.sh script to run Ruff and Black for linting and formatting. - Added test.sh script to execute tests with coverage reporting. - Configured .uv file for uv settings including lockfile management and dependency resolution. - Updated Makefile with targets for virtual environment setup, dependency installation, linting, testing, formatting, and database migrations. - Established requirements.txt with main and development dependencies for the project. --- .editorconfig | 4 +- .gitignore | 9 + .pre-commit-config.yaml | 92 +++ .prettierrc | 4 +- .scripts/lint.sh | 11 + .scripts/test.sh | 6 + .uv | 18 + .vscode/settings.json | 2 +- Dockerfile | 16 +- Makefile | 78 +++ PRETTIER_SETUP.md | 77 ++- README.md | 91 ++- TODO.md | 42 +- __init__.py | 5 - .../management/commands/create_sample_data.py | 4 +- dashboard_project/dashboard/utils.py | 10 +- .../dashboard_project/settings.py | 2 +- dashboard_project/static/css/dashboard.css | 358 +++++----- dashboard_project/static/css/style.css | 412 ++++++------ .../static/js/ajax-navigation.js | 475 ++++++------- .../static/js/ajax-pagination.js | 166 ++--- dashboard_project/static/js/dashboard.js | 484 ++++++------- dashboard_project/static/js/main.js | 256 +++---- .../templates/accounts/login.html | 44 +- .../templates/accounts/password_change.html | 56 +- .../accounts/password_change_done.html | 67 +- .../templates/accounts/profile.html | 382 ++++++----- .../templates/accounts/register.html | 44 +- dashboard_project/templates/base.html | 633 +++++++++--------- .../dashboard/chat_session_detail.html | 257 +++---- .../templates/dashboard/dashboard.html | 583 ++++++++-------- .../dashboard/dashboard_confirm_delete.html | 71 +- .../templates/dashboard/dashboard_form.html | 68 +- .../dashboard/data_source_confirm_delete.html | 85 +-- .../dashboard/data_source_detail.html | 457 +++++++------ .../templates/dashboard/data_view.html | 560 +++++++++------- .../templates/dashboard/no_company.html | 64 +- .../dashboard/partials/data_table.html | 304 ++++----- .../partials/search_results_table.html | 316 ++++----- .../templates/dashboard/search_results.html | 152 +++-- .../templates/dashboard/upload.html | 370 +++++----- docker-compose.yml | 3 + package-lock.json | 39 -- package.json | 19 +- pyproject.toml | 101 ++- requirements.txt | 320 +++++++++ setup.py | 19 - uv.lock | 459 ++++++++++++- 48 files changed, 4733 insertions(+), 3362 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100755 .scripts/lint.sh create mode 100755 .scripts/test.sh create mode 100644 .uv create mode 100644 Makefile delete mode 100644 __init__.py delete mode 100644 package-lock.json create mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/.editorconfig b/.editorconfig index 096689d..5291b78 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,8 @@ indent_size = 4 # HTML and Django/Jinja2 template files [*.{html,htm}] -indent_size = 2 +indent_style = tab +indent_size = 4 # Allow prettier to format Django/Jinja templates properly # The following comment options can be used in individual files if needed: # @@ -26,6 +27,7 @@ indent_size = 2 # CSS, JavaScript, and JSON files [*.{css,scss,js,json}] +indent_style = tab indent_size = 4 # Markdown files diff --git a/.gitignore b/.gitignore index 893a52d..6da3b4e 100644 --- a/.gitignore +++ b/.gitignore @@ -407,3 +407,12 @@ pyrightconfig.json *Zone.Identifier examples/ **/migrations/[0-9]**.py + +# UV specific +.uv/ +.uv-configs/ + +# Pyright and IDE specific +.vscode/ +.idea/ +.pyright/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5aa97b6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,92 @@ +default_install_hook_types: + - pre-commit + - post-checkout + - post-merge + - post-rewrite +repos: + # uv hooks for dependency management + - repo: https://github.com/astral-sh/uv-pre-commit + rev: 0.7.5 + hooks: + - id: uv-export + + # Standard pre-commit hooks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-json + - id: check-toml + - id: check-added-large-files + args: ["--maxkb=500"] + - id: detect-private-key + - id: check-merge-conflict + - id: check-case-conflict + - id: debug-statements + - id: mixed-line-ending + args: ["--fix=lf"] + + # # HTML/Django template linting + # - repo: https://github.com/rtts/djhtml + # rev: 3.0.7 + # hooks: + # - id: djhtml + # entry: djhtml --tabwidth 4 -- + # - id: djcss + # - id: djjs + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + types_or: [javascript, jsx, ts, tsx, css, scss, html, json, yaml, markdown] + additional_dependencies: + - prettier + - prettier-plugin-jinja-template + + # Ruff for linting and formatting + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.11.10 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + # Django-specific hooks + - repo: local + hooks: + - id: django-check + name: Django Check + entry: uv run python dashboard_project/manage.py check + language: system + pass_filenames: false + types: [python] + always_run: true + + - id: django-check-migrations + name: Django Check Migrations + entry: uv run python dashboard_project/manage.py makemigrations --check --dry-run + language: system + pass_filenames: false + types: [python] + + # Security checks + - repo: https://github.com/pycqa/bandit + rev: 1.8.3 + hooks: + - id: bandit + args: ["-c", "pyproject.toml", "-r", "dashboard_project"] + additional_dependencies: ["bandit[toml]"] + + # # Type checking + # - repo: https://github.com/pre-commit/mirrors-mypy + # rev: v1.15.0 + # hooks: + # - id: mypy + # additional_dependencies: + # - django-stubs>=5.0.2 + # - types-python-dateutil + # - types-requests + # - types-PyYAML diff --git a/.prettierrc b/.prettierrc index a17d342..d1fa5e7 100644 --- a/.prettierrc +++ b/.prettierrc @@ -11,9 +11,7 @@ "requirePragma": false, "semi": true, "singleQuote": false, - "tabWidth": 2, - "trailingComma": "es5", - "useTabs": false, + "useTabs": true, "overrides": [ { "files": ["*.html"], diff --git a/.scripts/lint.sh b/.scripts/lint.sh new file mode 100755 index 0000000..abdac16 --- /dev/null +++ b/.scripts/lint.sh @@ -0,0 +1,11 @@ +#!/bin/bash +# Run linting, formatting and type checking + +echo "Running Ruff linter..." +uv run -m ruff check dashboard_project + +echo "Running Ruff formatter..." +uv run -m ruff format dashboard_project + +echo "Running Black formatter..." +uv run -m black dashboard_project diff --git a/.scripts/test.sh b/.scripts/test.sh new file mode 100755 index 0000000..a6066fa --- /dev/null +++ b/.scripts/test.sh @@ -0,0 +1,6 @@ +#!/bin/bash +# Run tests with coverage + +echo "Running tests with coverage..." +uv run -m coverage run -m pytest +uv run -m coverage report -m diff --git a/.uv b/.uv new file mode 100644 index 0000000..647b153 --- /dev/null +++ b/.uv @@ -0,0 +1,18 @@ +[uv] +# Keep the uv.lock file up to date +keep-lockfile = true + +# Cache compiled bytecode for dependencies +compile-bytecode = true + +# Use a local cache directory +local-cache = true + +# Verbosity of output +verbosity = "minimal" + +# Define which part of the environment to check +environment-checks = ["python", "dependencies"] + +# How to resolve dependencies not specified with exact versions +dependency-resolution = "strict" diff --git a/.vscode/settings.json b/.vscode/settings.json index 7bf0e0f..304cca9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -43,5 +43,5 @@ "notebook.source.organizeImports": "explicit" }, "notebook.formatOnSave.enabled": true, - "prettier.requireConfig": true, + "prettier.requireConfig": true } diff --git a/Dockerfile b/Dockerfile index 4be687f..262ad61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,15 +10,25 @@ ENV DJANGO_SETTINGS_MODULE=dashboard_project.settings # Set work directory WORKDIR /app +# Install UV for Python package management +RUN pip install uv + +# Copy project files +COPY pyproject.toml . +COPY uv.lock . +COPY . . + # Install dependencies -COPY requirements.txt . RUN uv pip install -e . -# Copy project -COPY . . +# Change to the Django project directory +WORKDIR /app/dashboard_project # Collect static files RUN python manage.py collectstatic --noinput +# Change back to the app directory +WORKDIR /app + # Run gunicorn CMD ["gunicorn", "dashboard_project.wsgi:application", "--bind", "0.0.0.0:8000"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a53f2ef --- /dev/null +++ b/Makefile @@ -0,0 +1,78 @@ +.PHONY: venv install install-dev lint test format clean run migrate makemigrations superuser setup-node format-js + +# Create a virtual environment +venv: + uv venv -p 3.13 + +# Install production dependencies +install: + uv pip install -e . + +# Install development dependencies +install-dev: + uv pip install -e ".[dev]" + +# Run linting +lint: + uv run -m ruff check dashboard_project + +# Run tests +test: + uv run -m pytest + +# Format Python code +format: + uv run -m ruff format dashboard_project + uv run -m black dashboard_project + +# Format JavaScript/CSS/HTML files with Prettier +format-js: + npx run format + +# Setup Node.js dependencies +setup-node: + npm install + +# Clean Python cache files +clean: + find . -type d -name "__pycache__" -exec rm -rf {} + + find . -type f -name "*.pyc" -delete + find . -type f -name "*.pyo" -delete + find . -type f -name "*.pyd" -delete + find . -type d -name "*.egg-info" -exec rm -rf {} + + find . -type d -name "*.egg" -exec rm -rf {} + + find . -type d -name ".pytest_cache" -exec rm -rf {} + + find . -type d -name ".coverage" -exec rm -rf {} + + find . -type d -name "htmlcov" -exec rm -rf {} + + find . -type d -name ".ruff_cache" -exec rm -rf {} + + rm -rf build/ + rm -rf dist/ + +# Run the development server +run: + cd dashboard_project && uv run python manage.py runserver 8001 + +# Apply migrations +migrate: + cd dashboard_project && uv run python manage.py migrate + +# Create migrations +makemigrations: + cd dashboard_project && uv run python manage.py makemigrations + +# Create a superuser +superuser: + cd dashboard_project && uv run python manage.py createsuperuser + +# Update uv lock file +lock: + uv pip freeze > requirements.lock + +# Setup pre-commit hooks +setup-pre-commit: + uv pip install pre-commit + pre-commit install + +# Run pre-commit on all files +lint-all: + pre-commit run --all-files diff --git a/PRETTIER_SETUP.md b/PRETTIER_SETUP.md index 3e47249..8bec34b 100644 --- a/PRETTIER_SETUP.md +++ b/PRETTIER_SETUP.md @@ -1,37 +1,61 @@ -# Prettier for Django/Jinja Templates +# Prettier for Django Templates -This project uses Prettier with the `prettier-plugin-jinja-template` plugin to format HTML templates with Django/Jinja syntax. +This project uses Prettier with the `prettier-plugin-django-annotations` plugin to format HTML templates with Django template syntax. ## Setup -To use Prettier with your Django templates, you'll need to install Prettier and the Jinja template plugin: +The project is already configured with Prettier integration in pre-commit hooks. The configuration includes: + +1. `.prettierrc` - Configuration file with Django HTML support +2. `.prettierignore` - Files to exclude from formatting +3. Pre-commit hook for automatic formatting on commits + +### Manual Installation + +To use Prettier locally (outside of pre-commit hooks), you'll need to install the dependencies: ```bash # Using npm -npm install --save-dev prettier prettier-plugin-jinja-template +npm install -# Or using yarn -yarn add --dev prettier prettier-plugin-jinja-template +# Or install just the required packages +npm install --save-dev prettier prettier-plugin-django-annotations ``` ## Usage -Once installed, you can format your Django templates using: +### With Pre-commit + +Prettier will automatically run as part of the pre-commit hooks when you commit changes. + +To manually run the pre-commit hooks on all files: + +```bash +pre-commit run prettier --all-files +``` + +### Using npm Scripts + +The package.json includes npm scripts for formatting: + +```bash +# Format all static files +npm run format + +# Check formatting without modifying files +npm run format:check +``` + +### Command Line + +You can also run Prettier directly: ```bash # Format a specific file npx prettier --write path/to/template.html # Format all HTML files -npx prettier --write "**/*.html" -``` - -### Without install - -If you don't want to install the plugin, you can use the following command: - -```bash -npx prettier --plugin=prettier-plugin-jinja-template --parser=jinja-template --write **/*.html +npx prettier --write "dashboard_project/templates/**/*.html" ``` ## VSCode Integration @@ -40,12 +64,12 @@ For VSCode users, install the Prettier extension and add these settings to your ```json { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "[html]": { - "editor.defaultFormatter": "esbenp.prettier-vscode", - "editor.formatOnSave": true - }, - "prettier.requireConfig": true + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true + }, + "prettier.requireConfig": true } ``` @@ -62,3 +86,12 @@ If you need to prevent Prettier from formatting a section of your template: This works too. ``` + +## Django Template Support + +The `prettier-plugin-django-annotations` plugin provides special handling for Django templates, including: + +- Proper formatting of Django template tags (`{% %}`) +- Support for Django template comments (`{# #}`) +- Preservation of Django template variable output (`{{ }}`) +- Special handling for Django template syntax inside HTML attributes diff --git a/README.md b/README.md index a9f5e9e..2dd7dc9 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ A Django application that creates an analytics dashboard for chat session data. ## Requirements - Python 3.13+ -- Django 5.0+ -- PostgreSQL (optional, SQLite is fine for development) -- Other dependencies listed in [`pyproject.toml`](pyproject.toml) +- Django 5.2+ +- UV package manager (recommended) +- Other dependencies listed in [`pyproject.toml`](./pyproject.toml) ## Setup @@ -27,42 +27,103 @@ A Django application that creates an analytics dashboard for chat session data. ```sh git clone - cd dashboard_project + cd LiveGraphsDjango ``` -2. Create a virtual environment and activate it: +2. Install uv if you don't have it yet: + + ```sh + # Install using pip + pip install uv + + # Or with curl (Unix/macOS) + curl -sSf https://install.pypa.io/get-uv.py | python3 - + + # Or on Windows with PowerShell + irm https://install.pypa.io/get-uv.ps1 | iex + ``` + +3. Create a virtual environment and activate it: ```sh uv venv source .venv/bin/activate # On Windows: .venv\Scripts\activate ``` -3. Install dependencies: +4. Install dependencies: ```sh - uv pip install -r requirements.txt + # Install all dependencies including dev dependencies + uv pip install -e ".[dev]" + + # Or just runtime dependencies + uv pip install -e . ``` -4. Run migrations: +5. Run migrations: ```sh - uv run python manage.py makemigrations - uv run python manage.py migrate + cd dashboard_project + python manage.py makemigrations + python manage.py migrate ``` -5. Create a superuser: +6. Create a superuser: ```sh - uv run python manage.py createsuperuser + python manage.py createsuperuser ``` -6. Run the development server: +7. Run the development server: ```sh - uv run python manage.py runserver + python manage.py runserver ``` -7. Access the application at +8. Access the application at + +### Development Workflow with UV + +UV offers several advantages over traditional pip, including faster dependency resolution and installation: + +1. Running linting and formatting: + + ```sh + # Using the convenience script + ./.scripts/lint.sh + + # Or directly + uv run -m ruff check dashboard_project + uv run -m ruff format dashboard_project + uv run -m black dashboard_project + ``` + +2. Running tests: + + ```sh + # Using the convenience script + ./.scripts/test.sh + + # Or directly + uv run -m pytest + ``` + +3. Adding new dependencies: + + ```sh + # Add to project + uv pip install package_name + + # Then update pyproject.toml manually + # And update the lockfile + uv pip freeze > requirements.lock + ``` + +4. Updating the lockfile: + + ```sh + uv pip compile pyproject.toml -o uv.lock + ``` ### Using Docker diff --git a/TODO.md b/TODO.md index 509e2d0..5f50f0c 100644 --- a/TODO.md +++ b/TODO.md @@ -2,30 +2,30 @@ - When I zoom into the dasboard page, the graphs don't scale/adjust to fit the window until I completely refresh the page, can we solve that? - Add export functionality to the dashboard: - - File formats: - - CSV - - Excel - - JSON - - XML - - HTML - - PDF - - Make the export button a dropdown with the following options: - - Export as CSV - - Export as Excel - - Export as JSON - - Export as XML - - Export as HTML - - Export as PDF - - Make the export data section folded by default and only show the export button. - - Adjust the downloaded file name to include the company name, date and time of the export. + - File formats: + - CSV + - Excel + - JSON + - XML + - HTML + - PDF + - Make the export button a dropdown with the following options: + - Export as CSV + - Export as Excel + - Export as JSON + - Export as XML + - Export as HTML + - Export as PDF + - Make the export data section folded by default and only show the export button. + - Adjust the downloaded file name to include the company name, date and time of the export. - Add a button to download the CSV file for the selected company. - Make it possible to modify the column names in the CSV file through the admin interface. - Add possibility to add a company logo in the admin interface. -- Add periodic download from https://proto.notso.ai/jumbo/chats possibility for the jumbo company. - - Authentication: Basic Auth - - URL: https://proto.notso.ai/jumbo/chats - - Username: jumboadmin - - Password: jumboadmin +- Add periodic download from possibility for the XY company. + - Authentication: Basic Auth + - URL: + - Username: xxxx + - Password: xxxx - Reduce amount of rows in the table to fit the screen. - Add dark mode/theming to the dashboard. - Add Notso AI branding to the dashboard. diff --git a/__init__.py b/__init__.py deleted file mode 100644 index 4da1f97..0000000 --- a/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -""" -LiveGraphsDjango - Dashboard for analyzing chat session data. -""" - -__version__ = "0.1.0" diff --git a/dashboard_project/dashboard/management/commands/create_sample_data.py b/dashboard_project/dashboard/management/commands/create_sample_data.py index 098b67f..2af96a1 100644 --- a/dashboard_project/dashboard/management/commands/create_sample_data.py +++ b/dashboard_project/dashboard/management/commands/create_sample_data.py @@ -18,7 +18,7 @@ User = get_user_model() class Command(BaseCommand): help = "Create sample data for testing" - def handle(self, *args, **kwargs): + def handle(self, **_options): self.stdout.write("Creating sample data...") # Create admin user if it doesn't exist @@ -45,7 +45,7 @@ class Command(BaseCommand): self.stdout.write(f"Company already exists: {company.name}") # Create users for each company - for i, company in enumerate(companies): + for _i, company in enumerate(companies): # Company admin username = f"admin_{company.name.lower().replace(' ', '_')}" if not User.objects.filter(username=username).exists(): diff --git a/dashboard_project/dashboard/utils.py b/dashboard_project/dashboard/utils.py index 1a43368..135dddd 100644 --- a/dashboard_project/dashboard/utils.py +++ b/dashboard_project/dashboard/utils.py @@ -1,5 +1,7 @@ # dashboard/utils.py +import contextlib + import numpy as np import pandas as pd from django.db import models @@ -25,17 +27,13 @@ def process_csv_file(data_source): # Handle datetime fields start_time = None end_time = None - if "start_time" in row and pd.notna(row["start_time"]): - try: + with contextlib.suppress(Exception): start_time = make_aware(pd.to_datetime(row["start_time"])) - except Exception: - pass if "end_time" in row and pd.notna(row["end_time"]): - try: + with contextlib.suppress(Exception): end_time = make_aware(pd.to_datetime(row["end_time"])) - except Exception: pass # Convert boolean fields diff --git a/dashboard_project/dashboard_project/settings.py b/dashboard_project/dashboard_project/settings.py index 1549777..901db5c 100644 --- a/dashboard_project/dashboard_project/settings.py +++ b/dashboard_project/dashboard_project/settings.py @@ -7,7 +7,7 @@ from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-your-secret-key-here" +SECRET_KEY = "django-insecure-your-secret-key-here" # nosec: B105 # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True diff --git a/dashboard_project/static/css/dashboard.css b/dashboard_project/static/css/dashboard.css index 2f4b788..3401b43 100644 --- a/dashboard_project/static/css/dashboard.css +++ b/dashboard_project/static/css/dashboard.css @@ -4,277 +4,311 @@ /* Dashboard grid layout */ .dashboard-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); - /* Slightly larger minmax for widgets */ - gap: 1.5rem; - /* Increased gap */ + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + + /* Slightly larger minmax for widgets */ + gap: 1.5rem; + + /* Increased gap */ } /* Dashboard widget cards */ .dashboard-widget { - display: flex; - /* Allow flex for content alignment */ - flex-direction: column; - /* Stack header, body, footer vertically */ - height: 100%; - /* Ensure widgets fill grid cell height */ + display: flex; + + /* Allow flex for content alignment */ + flex-direction: column; + + /* Stack header, body, footer vertically */ + height: 100%; + + /* Ensure widgets fill grid cell height */ } .dashboard-widget .card-header { - display: flex; - justify-content: space-between; - align-items: center; + display: flex; + justify-content: space-between; + align-items: center; } .dashboard-widget .card-header .widget-title { - font-size: 1.1rem; - /* Slightly larger widget titles */ - font-weight: 600; + font-size: 1.1rem; + + /* Slightly larger widget titles */ + font-weight: 600; } .dashboard-widget .card-header .widget-actions { - display: flex; - gap: 0.5rem; + display: flex; + gap: 0.5rem; } .dashboard-widget .card-header .widget-actions .btn { - width: 32px; - /* Slightly larger action buttons */ - height: 32px; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.85rem; - background-color: transparent; - border: 1px solid transparent; - color: #6c757d; + width: 32px; + + /* Slightly larger action buttons */ + height: 32px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; + background-color: transparent; + border: 1px solid transparent; + color: #6c757d; } .dashboard-widget .card-header .widget-actions .btn:hover { - background-color: #f0f0f0; - border-color: #e0e0e0; - color: #333; + background-color: #f0f0f0; + border-color: #e0e0e0; + color: #333; } .dashboard-widget .card-body { - flex-grow: 1; - /* Allow card body to take available space */ - padding: 1.25rem; - /* Consistent padding */ + flex-grow: 1; + + /* Allow card body to take available space */ + padding: 1.25rem; + + /* Consistent padding */ } /* Chart widgets */ .chart-widget .card-body { - display: flex; - flex-direction: column; + display: flex; + flex-direction: column; } .chart-widget .chart-container { - flex: 1; - min-height: 250px; - /* Adjusted min-height */ - width: 100%; - /* Ensure it takes full width of card body */ + flex: 1; + min-height: 250px; + + /* Adjusted min-height */ + width: 100%; + + /* Ensure it takes full width of card body */ } /* Stat widgets / Stat Cards */ .stat-card { - text-align: center; - padding: 1.5rem; - /* Generous padding */ + text-align: center; + padding: 1.5rem; + + /* Generous padding */ } .stat-card .stat-icon { - font-size: 2.25rem; - /* Larger icon */ - margin-bottom: 1rem; - display: inline-block; - width: 4.5rem; - height: 4.5rem; - line-height: 4.5rem; - text-align: center; - border-radius: 50%; - background-color: #e9f2ff; - /* Light blue background for icon */ - color: #007bff; - /* Primary color for icon */ + font-size: 2.25rem; + + /* Larger icon */ + margin-bottom: 1rem; + display: inline-block; + width: 4.5rem; + height: 4.5rem; + line-height: 4.5rem; + text-align: center; + border-radius: 50%; + background-color: #e9f2ff; + + /* Light blue background for icon */ + color: #007bff; + + /* Primary color for icon */ } .stat-card .stat-value { - font-size: 2.25rem; - /* Larger stat value */ - font-weight: 700; - margin-bottom: 0.25rem; - /* Reduced margin */ - line-height: 1.1; - color: #212529; - /* Darker color for value */ + font-size: 2.25rem; + + /* Larger stat value */ + font-weight: 700; + margin-bottom: 0.25rem; + + /* Reduced margin */ + line-height: 1.1; + color: #212529; + + /* Darker color for value */ } .stat-card .stat-label { - font-size: 0.9rem; - /* Slightly larger label */ - color: #6c757d; - margin-bottom: 0; + font-size: 0.9rem; + + /* Slightly larger label */ + color: #6c757d; + margin-bottom: 0; } /* Dashboard theme variations */ .dashboard-theme-light .card { - background-color: #ffffff; + background-color: #fff; } .dashboard-theme-dark { - background-color: #212529; - color: #f8f9fa; + background-color: #212529; + color: #f8f9fa; } .dashboard-theme-dark .card { - background-color: #343a40; - color: #f8f9fa; - border-color: #495057; + background-color: #343a40; + color: #f8f9fa; + border-color: #495057; } .dashboard-theme-dark .card-header { - background-color: #495057; - border-bottom-color: #6c757d; + background-color: #495057; + border-bottom-color: #6c757d; } .dashboard-theme-dark .stat-card .stat-label { - color: #adb5bd; + color: #adb5bd; } /* Time period selector */ .time-period-selector { - display: flex; - align-items: center; - gap: 0.75rem; - /* Increased gap */ - margin-bottom: 1.5rem; - /* Increased margin */ + display: flex; + align-items: center; + gap: 0.75rem; + + /* Increased gap */ + margin-bottom: 1.5rem; + + /* Increased margin */ } .time-period-selector .btn-group { - flex-wrap: wrap; + flex-wrap: wrap; } .time-period-selector .btn { - padding: 0.375rem 0.75rem; - /* Bootstrap-like padding */ - font-size: 0.875rem; + padding: 0.375rem 0.75rem; + + /* Bootstrap-like padding */ + font-size: 0.875rem; } /* Custom metric selector */ .metric-selector { - max-width: 100%; - overflow-x: auto; - white-space: nowrap; - padding-bottom: 0.5rem; - margin-bottom: 1rem; + max-width: 100%; + overflow-x: auto; + white-space: nowrap; + padding-bottom: 0.5rem; + margin-bottom: 1rem; } .metric-selector .nav-link { - white-space: nowrap; - padding: 0.5rem 1rem; - font-weight: 500; + white-space: nowrap; + padding: 0.5rem 1rem; + font-weight: 500; } .metric-selector .nav-link.active { - background-color: #007bff; - color: white; - border-radius: 0.25rem; + background-color: #007bff; + color: white; + border-radius: 0.25rem; } /* Dashboard loading states */ .widget-placeholder { - min-height: 300px; - background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%); - /* Lighter gradient */ - background-size: 200% 100%; - animation: loading 1.8s infinite ease-in-out; - /* Smoother animation */ - border-radius: 0.5rem; - /* Consistent with cards */ + min-height: 300px; + background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%); + + /* Lighter gradient */ + background-size: 200% 100%; + animation: loading 1.8s infinite ease-in-out; + + /* Smoother animation */ + border-radius: 0.5rem; + + /* Consistent with cards */ } @keyframes loading { - 0% { - background-position: 200% 0; - } + 0% { + background-position: 200% 0; + } - 100% { - background-position: -200% 0; - } + 100% { + background-position: -200% 0; + } } /* Dashboard empty states */ .empty-state { - padding: 2.5rem; - /* Increased padding */ - text-align: center; - color: #6c757d; - background-color: #f8f9fa; - /* Light background for empty state */ - border-radius: 0.5rem; - border: 1px dashed #ced4da; - /* Dashed border */ + padding: 2.5rem; + + /* Increased padding */ + text-align: center; + color: #6c757d; + background-color: #f8f9fa; + + /* Light background for empty state */ + border-radius: 0.5rem; + border: 1px dashed #ced4da; + + /* Dashed border */ } .empty-state .empty-state-icon { - font-size: 3.5rem; - /* Larger icon */ - margin-bottom: 1.5rem; - opacity: 0.4; + font-size: 3.5rem; + + /* Larger icon */ + margin-bottom: 1.5rem; + opacity: 0.4; } .empty-state .empty-state-message { - font-size: 1.2rem; - /* Slightly larger message */ - margin-bottom: 1.5rem; - font-weight: 500; + font-size: 1.2rem; + + /* Slightly larger message */ + margin-bottom: 1.5rem; + font-weight: 500; } .empty-state .btn { - margin-top: 1rem; + margin-top: 1rem; } /* Responsive adjustments */ -@media (max-width: 767.98px) { - .dashboard-grid { - grid-template-columns: 1fr; - } +@media (width <=767.98px) { + .dashboard-grid { + grid-template-columns: 1fr; + } - .stat-card { - padding: 1rem; - } + .stat-card { + padding: 1rem; + } - .stat-card .stat-icon { - font-size: 1.5rem; - width: 3rem; - height: 3rem; - line-height: 3rem; - } + .stat-card .stat-icon { + font-size: 1.5rem; + width: 3rem; + height: 3rem; + line-height: 3rem; + } - .stat-card .stat-value { - font-size: 1.5rem; - } + .stat-card .stat-value { + font-size: 1.5rem; + } } /* --- Stat Boxes Alignment Fix (Bottom Align, No Overlap) --- */ .stats-row { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - align-items: stretch; + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + align-items: stretch; } + .stats-card { - flex: 1 1 0; - min-width: 200px; - display: flex; - flex-direction: column; - justify-content: flex-end; /* Push content to bottom */ - align-items: flex-start; - box-sizing: border-box; - /* Remove min-height/height for natural stretch */ + flex: 1 1 0; + min-width: 200px; + display: flex; + flex-direction: column; + justify-content: flex-end; + + /* Push content to bottom */ + align-items: flex-start; + box-sizing: border-box; + + /* Remove min-height/height for natural stretch */ } diff --git a/dashboard_project/static/css/style.css b/dashboard_project/static/css/style.css index 51a347d..e30d0a3 100644 --- a/dashboard_project/static/css/style.css +++ b/dashboard_project/static/css/style.css @@ -4,325 +4,361 @@ /* General Styles */ body { - font-family: - -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; - background-color: #f4f7f9; - /* Lighter, cleaner background */ - color: #333; - /* Darker text for better contrast */ - line-height: 1.6; - display: flex; - /* Added for sticky footer */ - flex-direction: column; - /* Added for sticky footer */ - min-height: 100vh; - /* Ensures body takes at least full viewport height */ + font-family: + -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; + background-color: #f4f7f9; + + /* Lighter, cleaner background */ + color: #333; + + /* Darker text for better contrast */ + line-height: 1.6; + display: flex; + + /* Added for sticky footer */ + flex-direction: column; + + /* Added for sticky footer */ + min-height: 100vh; + + /* Ensures body takes at least full viewport height */ } /* Navbar adjustments (if needed, Bootstrap usually handles this well) */ .navbar { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - /* Subtle shadow for depth */ + box-shadow: 0 2px 4px rgb(0 0 0 / 5%); + + /* Subtle shadow for depth */ } /* Helper Classes */ .text-truncate-2 { - display: -webkit-box; - -webkit-line-clamp: 2; - line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; } .cursor-pointer { - cursor: pointer; + cursor: pointer; } .min-w-150 { - min-width: 150px; + min-width: 150px; } /* Card styles */ .card { - border: 1px solid #e0e5e9; - /* Lighter border */ - border-radius: 0.5rem; - /* Slightly more rounded corners */ - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); - /* Softer, more modern shadow */ - transition: - transform 0.2s ease-in-out, - box-shadow 0.2s ease-in-out; - margin-bottom: 1.5rem; - /* Consistent margin */ + border: 1px solid #e0e5e9; + + /* Lighter border */ + border-radius: 0.5rem; + + /* Slightly more rounded corners */ + box-shadow: 0 4px 12px rgb(0 0 0 / 8%); + + /* Softer, more modern shadow */ + transition: + transform 0.2s ease-in-out, + box-shadow 0.2s ease-in-out; + margin-bottom: 1.5rem; + + /* Consistent margin */ } .card-hover:hover { - transform: translateY(-3px); - box-shadow: 0 6px 16px rgba(0, 0, 0, 0.1); + transform: translateY(-3px); + box-shadow: 0 6px 16px rgb(0 0 0 / 10%); } .card-header { - background-color: #ffffff; - /* Clean white header */ - border-bottom: 1px solid #e0e5e9; - font-weight: 500; - /* Slightly bolder header text */ - padding: 0.75rem 1.25rem; + background-color: #fff; + + /* Clean white header */ + border-bottom: 1px solid #e0e5e9; + font-weight: 500; + + /* Slightly bolder header text */ + padding: 0.75rem 1.25rem; } .card-title { - font-size: 1.15rem; - /* Adjusted card title size */ - font-weight: 600; + font-size: 1.15rem; + + /* Adjusted card title size */ + font-weight: 600; } /* Sidebar enhancements */ .sidebar { - background-color: #ffffff; - /* White sidebar for a cleaner look */ - border-right: 1px solid #e0e5e9; - box-shadow: 2px 0 5px rgba(0, 0, 0, 0.03); - transition: all 0.3s; + background-color: #fff; + + /* White sidebar for a cleaner look */ + border-right: 1px solid #e0e5e9; + box-shadow: 2px 0 5px rgb(0 0 0 / 3%); + transition: all 0.3s; } .sidebar-sticky { - padding-top: 1rem; + padding-top: 1rem; } .sidebar .nav-link { - color: #4a5568; - /* Softer link color */ - padding: 0.65rem 1.25rem; - /* Adjusted padding */ - border-radius: 0.375rem; - /* Bootstrap-like rounded corners for links */ - margin: 0.1rem 0.5rem; - /* Margin around links */ - font-weight: 500; + color: #4a5568; + + /* Softer link color */ + padding: 0.65rem 1.25rem; + + /* Adjusted padding */ + border-radius: 0.375rem; + + /* Bootstrap-like rounded corners for links */ + margin: 0.1rem 0.5rem; + + /* Margin around links */ + font-weight: 500; } .sidebar .nav-link:hover { - color: #007bff; - /* Primary color on hover */ - background-color: #e9f2ff; - /* Light blue background on hover */ + color: #007bff; + + /* Primary color on hover */ + background-color: #e9f2ff; + + /* Light blue background on hover */ } .sidebar .nav-link.active { - color: #007bff; - background-color: #d6e4ff; - /* Slightly darker blue for active */ - font-weight: 600; + color: #007bff; + background-color: #d6e4ff; + + /* Slightly darker blue for active */ + font-weight: 600; } .sidebar .nav-link i.me-2 { - width: 20px; - /* Ensure icons align well */ - text-align: center; - margin-right: 0.75rem !important; - /* Consistent icon spacing */ + width: 20px; + + /* Ensure icons align well */ + text-align: center; + margin-right: 0.75rem !important; + + /* Consistent icon spacing */ } .sidebar .nav-header { - font-size: 0.8rem; - text-transform: uppercase; - letter-spacing: 0.08em; - color: #718096; - /* Softer header color */ - padding: 0.5rem 1.25rem; - margin-top: 1rem; + font-size: 0.8rem; + text-transform: uppercase; + letter-spacing: 0.08em; + color: #718096; + + /* Softer header color */ + padding: 0.5rem 1.25rem; + margin-top: 1rem; } /* Dashboard stats cards */ .stats-card { - border-radius: 0.5rem; - overflow: hidden; + border-radius: 0.5rem; + overflow: hidden; } .stats-card h3 { - font-size: 1.75rem; - font-weight: 600; + font-size: 1.75rem; + font-weight: 600; } .stats-card p { - font-size: 0.875rem; - margin-bottom: 0; - opacity: 0.8; + font-size: 0.875rem; + margin-bottom: 0; + opacity: 0.8; } /* Chart containers */ .chart-container { - width: 100%; - height: 300px; - position: relative; + width: 100%; + height: 300px; + position: relative; } /* Loading overlay */ .loading-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(255, 255, 255, 0.7); - display: flex; - justify-content: center; - align-items: center; - z-index: 9999; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgb(255 255 255 / 70%); + display: flex; + justify-content: center; + align-items: center; + z-index: 9999; } /* Table enhancements */ .table { - border-color: #e0e5e9; + border-color: #e0e5e9; } .table th { - font-weight: 600; - /* Bolder table headers */ - color: #4a5568; - background-color: #f8f9fc; - /* Light background for headers */ + font-weight: 600; + + /* Bolder table headers */ + color: #4a5568; + background-color: #f8f9fc; + + /* Light background for headers */ } .table-striped tbody tr:nth-of-type(odd) { - background-color: rgba(0, 0, 0, 0.02); - /* Very subtle striping */ + background-color: rgb(0 0 0 / 2%); + + /* Very subtle striping */ } .table-hover tbody tr:hover { - background-color: #e9f2ff; - /* Consistent hover with sidebar */ + background-color: #e9f2ff; + + /* Consistent hover with sidebar */ } /* Form improvements */ .form-control, .form-select { - border-color: #ced4da; - border-radius: 0.375rem; - /* Consistent border radius */ - padding: 0.5rem 0.75rem; - /* Adjusted padding */ + border-color: #ced4da; + border-radius: 0.375rem; + + /* Consistent border radius */ + padding: 0.5rem 0.75rem; + + /* Adjusted padding */ } .form-control:focus, .form-select:focus { - border-color: #86b7fe; - /* Bootstrap focus color */ - box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); - /* Bootstrap focus shadow */ + border-color: #86b7fe; + + /* Bootstrap focus color */ + box-shadow: 0 0 0 0.25rem rgb(13 110 253 / 25%); + + /* Bootstrap focus shadow */ } /* Button styling */ .btn { - border-radius: 0.375rem; - /* Consistent border radius */ - padding: 0.5rem 1rem; - /* Standard button padding */ - font-weight: 500; - transition: - background-color 0.15s ease-in-out, - border-color 0.15s ease-in-out, - box-shadow 0.15s ease-in-out; + border-radius: 0.375rem; + + /* Consistent border radius */ + padding: 0.5rem 1rem; + + /* Standard button padding */ + font-weight: 500; + transition: + background-color 0.15s ease-in-out, + border-color 0.15s ease-in-out, + box-shadow 0.15s ease-in-out; } .btn-primary { - background-color: #007bff; - border-color: #007bff; + background-color: #007bff; + border-color: #007bff; } .btn-primary:hover { - background-color: #0069d9; - border-color: #0062cc; + background-color: #0069d9; + border-color: #0062cc; } .btn-secondary { - background-color: #6c757d; - border-color: #6c757d; + background-color: #6c757d; + border-color: #6c757d; } .btn-secondary:hover { - background-color: #5a6268; - border-color: #545b62; + background-color: #5a6268; + border-color: #545b62; } /* Alert styling */ .alert { - border-radius: 0.375rem; - padding: 0.9rem 1.25rem; + border-radius: 0.375rem; + padding: 0.9rem 1.25rem; } /* Chat transcript styling */ .chat-transcript { - background-color: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 0.25rem; - padding: 1rem; - max-height: 500px; - overflow-y: auto; - font-size: 0.875rem; + background-color: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 0.25rem; + padding: 1rem; + max-height: 500px; + overflow-y: auto; + font-size: 0.875rem; } .chat-transcript pre { - white-space: pre-wrap; - font-family: inherit; - margin-bottom: 0; + white-space: pre-wrap; + font-family: inherit; + margin-bottom: 0; } /* Footer styling */ footer { - background-color: #ffffff; - /* White footer */ - border-top: 1px solid #e0e5e9; - padding: 1.5rem 0; - color: #6c757d; - font-size: 0.9rem; - margin-top: auto; - /* Added for sticky footer */ + background-color: #fff; + + /* White footer */ + border-top: 1px solid #e0e5e9; + padding: 1.5rem 0; + color: #6c757d; + font-size: 0.9rem; + margin-top: auto; + + /* Added for sticky footer */ } /* Responsive adjustments */ -@media (max-width: 767.98px) { - .main-content { - margin-left: 0; - } +@media (width <=767.98px) { + .main-content { + margin-left: 0; + } - .stats-card h3 { - font-size: 1.5rem; - } + .stats-card h3 { + font-size: 1.5rem; + } - .chart-container { - height: 250px; - } + .chart-container { + height: 250px; + } - .card-title { - font-size: 1.25rem; - } + .card-title { + font-size: 1.25rem; + } } /* Print styles */ @media print { - .sidebar, - .navbar, - .btn, - footer { - display: none !important; - } + .sidebar, + .navbar, + .btn, + footer { + display: none !important; + } - .main-content { - margin-left: 0 !important; - padding: 0 !important; - } + .main-content { + margin-left: 0 !important; + padding: 0 !important; + } - .card { - break-inside: avoid; - border: none !important; - box-shadow: none !important; - } + .card { + break-inside: avoid; + border: none !important; + box-shadow: none !important; + } - .chart-container { - break-inside: avoid; - height: auto !important; - } + .chart-container { + break-inside: avoid; + height: auto !important; + } } diff --git a/dashboard_project/static/js/ajax-navigation.js b/dashboard_project/static/js/ajax-navigation.js index 9a7d57a..4b46307 100644 --- a/dashboard_project/static/js/ajax-navigation.js +++ b/dashboard_project/static/js/ajax-navigation.js @@ -6,268 +6,269 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Only initialize if AJAX navigation is enabled - if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) { - setupAjaxNavigation(); - } + // Only initialize if AJAX navigation is enabled + if (typeof ENABLE_AJAX_NAVIGATION !== "undefined" && ENABLE_AJAX_NAVIGATION) { + setupAjaxNavigation(); + } - // Function to set up AJAX navigation for the application - function setupAjaxNavigation() { - // Configuration - const config = { - mainContentSelector: "#main-content", // Selector for the main content area - navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX - loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator - excludePatterns: [ - // URL patterns to exclude from AJAX navigation - /\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads - /\/admin\//, // Admin pages - /\/accounts\/logout\//, // Logout page - /\/api\//, // API endpoints - ], - }; + // Function to set up AJAX navigation for the application + function setupAjaxNavigation() { + // Configuration + const config = { + mainContentSelector: "#main-content", // Selector for the main content area + navLinkSelector: ".ajax-nav-link", // Selector for links to handle with AJAX + loadingIndicatorId: "nav-loading-indicator", // ID of the loading indicator + excludePatterns: [ + // URL patterns to exclude from AJAX navigation + /\.(pdf|xlsx?|docx?|csv|zip|png|jpe?g|gif|svg)$/i, // File downloads + /\/admin\//, // Admin pages + /\/accounts\/logout\//, // Logout page + /\/api\//, // API endpoints + ], + }; - // Create and insert the loading indicator - if (!document.getElementById(config.loadingIndicatorId)) { - const loadingIndicator = document.createElement("div"); - loadingIndicator.id = config.loadingIndicatorId; - loadingIndicator.className = "position-fixed top-0 start-0 end-0"; - loadingIndicator.innerHTML = - '
'; - loadingIndicator.style.display = "none"; - loadingIndicator.style.zIndex = "9999"; - document.body.appendChild(loadingIndicator); - } + // Create and insert the loading indicator + if (!document.getElementById(config.loadingIndicatorId)) { + const loadingIndicator = document.createElement("div"); + loadingIndicator.id = config.loadingIndicatorId; + loadingIndicator.className = "position-fixed top-0 start-0 end-0"; + loadingIndicator.innerHTML = + '
'; + loadingIndicator.style.display = "none"; + loadingIndicator.style.zIndex = "9999"; + document.body.appendChild(loadingIndicator); + } - // Get the loading indicator element - const loadingIndicator = document.getElementById(config.loadingIndicatorId); + // Get the loading indicator element + const loadingIndicator = document.getElementById(config.loadingIndicatorId); - // Get the main content container - const mainContent = document.querySelector(config.mainContentSelector); - if (!mainContent) { - console.warn("Main content container not found. AJAX navigation disabled."); - return; - } + // Get the main content container + const mainContent = document.querySelector(config.mainContentSelector); + if (!mainContent) { + console.warn("Main content container not found. AJAX navigation disabled."); + return; + } - // Function to check if a URL should be excluded from AJAX navigation - function shouldExcludeUrl(url) { - for (const pattern of config.excludePatterns) { - if (pattern.test(url)) { - return true; - } - } - return false; - } + // Function to check if a URL should be excluded from AJAX navigation + function shouldExcludeUrl(url) { + for (const pattern of config.excludePatterns) { + if (pattern.test(url)) { + return true; + } + } + return false; + } - // Function to show the loading indicator - function showLoading() { - loadingIndicator.style.display = "block"; - } + // Function to show the loading indicator + function showLoading() { + loadingIndicator.style.display = "block"; + } - // Function to hide the loading indicator - function hideLoading() { - loadingIndicator.style.display = "none"; - } + // Function to hide the loading indicator + function hideLoading() { + loadingIndicator.style.display = "none"; + } - // Function to handle AJAX page navigation - function handlePageNavigation(url, pushState = true) { - if (shouldExcludeUrl(url)) { - window.location.href = url; - return; - } - showLoading(); - const currentScrollPos = window.scrollY; - fetch(url, { - headers: { - "X-Requested-With": "XMLHttpRequest", - "X-AJAX-Navigation": "true", - Accept: "text/html", - }, - }) - .then((response) => { - if (!response.ok) throw new Error(`Network response was not ok: ${response.status}`); - return response.text(); - }) - .then((html) => { - // Parse the HTML and extract #main-content - const tempDiv = document.createElement("div"); - tempDiv.innerHTML = html; - const newContent = tempDiv.querySelector(config.mainContentSelector); - if (!newContent) throw new Error("Could not find main content in the response"); - mainContent.innerHTML = newContent.innerHTML; - // Update the page title - const titleMatch = html.match(/(.*?)<\/title>/i); - if (titleMatch) document.title = titleMatch[1]; - // Re-initialize dynamic content - reloadScripts(mainContent); - attachEventListeners(); - initializePageScripts(); - if (pushState) { - history.pushState( - { url: url, title: document.title, scrollPos: currentScrollPos }, - document.title, - url - ); - window.scrollTo({ top: 0, behavior: "smooth" }); - } else if (window.history.state && window.history.state.scrollPos) { - window.scrollTo({ top: window.history.state.scrollPos }); - } - hideLoading(); - }) - .catch((error) => { - console.error("Error during AJAX navigation:", error); - hideLoading(); - window.location.href = url; - }); - } + // Function to handle AJAX page navigation + function handlePageNavigation(url, pushState = true) { + if (shouldExcludeUrl(url)) { + window.location.href = url; + return; + } + showLoading(); + const currentScrollPos = window.scrollY; + fetch(url, { + headers: { + "X-Requested-With": "XMLHttpRequest", + "X-AJAX-Navigation": "true", + Accept: "text/html", + }, + }) + .then((response) => { + if (!response.ok) + throw new Error(`Network response was not ok: ${response.status}`); + return response.text(); + }) + .then((html) => { + // Parse the HTML and extract #main-content + const tempDiv = document.createElement("div"); + tempDiv.innerHTML = html; + const newContent = tempDiv.querySelector(config.mainContentSelector); + if (!newContent) throw new Error("Could not find main content in the response"); + mainContent.innerHTML = newContent.innerHTML; + // Update the page title + const titleMatch = html.match(/<title>(.*?)<\/title>/i); + if (titleMatch) document.title = titleMatch[1]; + // Re-initialize dynamic content + reloadScripts(mainContent); + attachEventListeners(); + initializePageScripts(); + if (pushState) { + history.pushState( + { url: url, title: document.title, scrollPos: currentScrollPos }, + document.title, + url, + ); + window.scrollTo({ top: 0, behavior: "smooth" }); + } else if (window.history.state && window.history.state.scrollPos) { + window.scrollTo({ top: window.history.state.scrollPos }); + } + hideLoading(); + }) + .catch((error) => { + console.error("Error during AJAX navigation:", error); + hideLoading(); + window.location.href = url; + }); + } - // Function to reload and execute scripts in new content - function reloadScripts(container) { - const scripts = container.getElementsByTagName("script"); - for (let script of scripts) { - const newScript = document.createElement("script"); + // Function to reload and execute scripts in new content + function reloadScripts(container) { + const scripts = container.getElementsByTagName("script"); + for (let script of scripts) { + const newScript = document.createElement("script"); - // Copy all attributes - Array.from(script.attributes).forEach((attr) => { - newScript.setAttribute(attr.name, attr.value); - }); + // Copy all attributes + Array.from(script.attributes).forEach((attr) => { + newScript.setAttribute(attr.name, attr.value); + }); - // Copy inline script content - newScript.textContent = script.textContent; + // Copy inline script content + newScript.textContent = script.textContent; - // Replace old script with new one - script.parentNode.replaceChild(newScript, script); - } - } + // Replace old script with new one + script.parentNode.replaceChild(newScript, script); + } + } - // Function to handle form submissions - function handleFormSubmission(form, e) { - e.preventDefault(); + // Function to handle form submissions + function handleFormSubmission(form, e) { + e.preventDefault(); - // Show loading indicator - showLoading(); + // Show loading indicator + showLoading(); - // Get form data - const formData = new FormData(form); - const method = form.method.toLowerCase(); - const url = form.action || window.location.href; + // Get form data + const formData = new FormData(form); + const method = form.method.toLowerCase(); + const url = form.action || window.location.href; - // Configure fetch options - const fetchOptions = { - method: method, - headers: { - "X-AJAX-Navigation": "true", - }, - }; + // Configure fetch options + const fetchOptions = { + method: method, + headers: { + "X-AJAX-Navigation": "true", + }, + }; - // Handle different HTTP methods - if (method === "get") { - const queryParams = new URLSearchParams(formData).toString(); - handlePageNavigation(url + (queryParams ? "?" + queryParams : "")); - } else { - fetchOptions.body = formData; + // Handle different HTTP methods + if (method === "get") { + const queryParams = new URLSearchParams(formData).toString(); + handlePageNavigation(url + (queryParams ? "?" + queryParams : "")); + } else { + fetchOptions.body = formData; - fetch(url, fetchOptions) - .then((response) => { - if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); - return response.json(); - }) - .then((data) => { - if (data.redirect) { - // Handle server-side redirects - handlePageNavigation(data.redirect, true); - } else { - // Update page content - mainContent.innerHTML = data.html; - document.title = data.title || document.title; + fetch(url, fetchOptions) + .then((response) => { + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + return response.json(); + }) + .then((data) => { + if (data.redirect) { + // Handle server-side redirects + handlePageNavigation(data.redirect, true); + } else { + // Update page content + mainContent.innerHTML = data.html; + document.title = data.title || document.title; - // Re-initialize dynamic content - reloadScripts(mainContent); - attachEventListeners(); - initializePageScripts(); + // Re-initialize dynamic content + reloadScripts(mainContent); + attachEventListeners(); + initializePageScripts(); - // Update URL if needed - if (data.url) { - history.pushState({ url: data.url }, document.title, data.url); - } - } - }) - .catch((error) => { - console.error("Form submission error:", error); - // Fallback to traditional form submission - form.submit(); - }) - .finally(() => { - hideLoading(); - }); - } - } + // Update URL if needed + if (data.url) { + history.pushState({ url: data.url }, document.title, data.url); + } + } + }) + .catch((error) => { + console.error("Form submission error:", error); + // Fallback to traditional form submission + form.submit(); + }) + .finally(() => { + hideLoading(); + }); + } + } - // Function to initialize scripts needed for the new page content - function initializePageScripts() { - // Re-initialize any custom scripts that might be needed - if (typeof setupAjaxPagination === "function") { - setupAjaxPagination(); - } + // Function to initialize scripts needed for the new page content + function initializePageScripts() { + // Re-initialize any custom scripts that might be needed + if (typeof setupAjaxPagination === "function") { + setupAjaxPagination(); + } - // Initialize Bootstrap tooltips, popovers, etc. - if (typeof bootstrap !== "undefined") { - // Initialize tooltips - const tooltipTriggerList = [].slice.call( - document.querySelectorAll('[data-bs-toggle="tooltip"]') - ); - tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl); - }); + // Initialize Bootstrap tooltips, popovers, etc. + if (typeof bootstrap !== "undefined") { + // Initialize tooltips + const tooltipTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="tooltip"]'), + ); + tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); - // Initialize popovers - const popoverTriggerList = [].slice.call( - document.querySelectorAll('[data-bs-toggle="popover"]') - ); - popoverTriggerList.map(function (popoverTriggerEl) { - return new bootstrap.Popover(popoverTriggerEl); - }); - } - } + // Initialize popovers + const popoverTriggerList = [].slice.call( + document.querySelectorAll('[data-bs-toggle="popover"]'), + ); + popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl); + }); + } + } - // Function to attach event listeners to forms and links - function attachEventListeners() { - // Handle AJAX navigation links - document.querySelectorAll(config.navLinkSelector).forEach((link) => { - if (!link.dataset.ajaxNavInitialized) { - link.addEventListener("click", function (e) { - if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) { - return; // Let the browser handle these cases - } - e.preventDefault(); - handlePageNavigation(this.href); - }); - link.dataset.ajaxNavInitialized = "true"; - } - }); + // Function to attach event listeners to forms and links + function attachEventListeners() { + // Handle AJAX navigation links + document.querySelectorAll(config.navLinkSelector).forEach((link) => { + if (!link.dataset.ajaxNavInitialized) { + link.addEventListener("click", function (e) { + if (e.ctrlKey || e.metaKey || e.shiftKey || shouldExcludeUrl(this.href)) { + return; // Let the browser handle these cases + } + e.preventDefault(); + handlePageNavigation(this.href); + }); + link.dataset.ajaxNavInitialized = "true"; + } + }); - // Handle forms with AJAX - document - .querySelectorAll("form.ajax-form, form.search-form, form.filter-form") - .forEach((form) => { - if (!form.dataset.ajaxFormInitialized) { - form.addEventListener("submit", (e) => handleFormSubmission(form, e)); - form.dataset.ajaxFormInitialized = "true"; - } - }); - } + // Handle forms with AJAX + document + .querySelectorAll("form.ajax-form, form.search-form, form.filter-form") + .forEach((form) => { + if (!form.dataset.ajaxFormInitialized) { + form.addEventListener("submit", (e) => handleFormSubmission(form, e)); + form.dataset.ajaxFormInitialized = "true"; + } + }); + } - // Initial attachment of event listeners - attachEventListeners(); + // Initial attachment of event listeners + attachEventListeners(); - // Handle browser back/forward buttons - window.addEventListener("popstate", function (event) { - if (event.state && event.state.url) { - handlePageNavigation(event.state.url, false); - } else { - // Fallback to current URL if no state - handlePageNavigation(window.location.href, false); - } - }); - } + // Handle browser back/forward buttons + window.addEventListener("popstate", function (event) { + if (event.state && event.state.url) { + handlePageNavigation(event.state.url, false); + } else { + // Fallback to current URL if no state + handlePageNavigation(window.location.href, false); + } + }); + } }); diff --git a/dashboard_project/static/js/ajax-pagination.js b/dashboard_project/static/js/ajax-pagination.js index bcec9a7..16f1889 100644 --- a/dashboard_project/static/js/ajax-pagination.js +++ b/dashboard_project/static/js/ajax-pagination.js @@ -6,101 +6,101 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Initialize AJAX pagination - setupAjaxPagination(); + // Initialize AJAX pagination + setupAjaxPagination(); - // Function to set up AJAX pagination for the entire application - function setupAjaxPagination() { - // Configuration - can be customized per page if needed - const config = { - contentContainerId: "ajax-content-container", // ID of the container to update - loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner - paginationLinkClass: "pagination-link", // Class for pagination links - retryMessage: "An error occurred while loading data. Please try again.", - }; + // Function to set up AJAX pagination for the entire application + function setupAjaxPagination() { + // Configuration - can be customized per page if needed + const config = { + contentContainerId: "ajax-content-container", // ID of the container to update + loadingSpinnerId: "ajax-loading-spinner", // ID of the loading spinner + paginationLinkClass: "pagination-link", // Class for pagination links + retryMessage: "An error occurred while loading data. Please try again.", + }; - // Get container elements - const contentContainer = document.getElementById(config.contentContainerId); - const loadingSpinner = document.getElementById(config.loadingSpinnerId); + // Get container elements + const contentContainer = document.getElementById(config.contentContainerId); + const loadingSpinner = document.getElementById(config.loadingSpinnerId); - // Exit if the page doesn't have the required elements - if (!contentContainer || !loadingSpinner) return; + // Exit if the page doesn't have the required elements + if (!contentContainer || !loadingSpinner) return; - // Function to handle pagination clicks - function setupPaginationListeners() { - document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => { - link.addEventListener("click", function (e) { - e.preventDefault(); - handleAjaxNavigation(this.href); + // Function to handle pagination clicks + function setupPaginationListeners() { + document.querySelectorAll("." + config.paginationLinkClass).forEach((link) => { + link.addEventListener("click", function (e) { + e.preventDefault(); + handleAjaxNavigation(this.href); - // Get the page number if available - const page = this.getAttribute("data-page"); + // Get the page number if available + const page = this.getAttribute("data-page"); - // Update browser URL without refreshing - const newUrl = this.href; - history.pushState({ url: newUrl, page: page }, "", newUrl); - }); - }); - } + // Update browser URL without refreshing + const newUrl = this.href; + history.pushState({ url: newUrl, page: page }, "", newUrl); + }); + }); + } - // Function to handle AJAX navigation - function handleAjaxNavigation(url) { - // Show loading spinner - contentContainer.classList.add("d-none"); - loadingSpinner.classList.remove("d-none"); + // Function to handle AJAX navigation + function handleAjaxNavigation(url) { + // Show loading spinner + contentContainer.classList.add("d-none"); + loadingSpinner.classList.remove("d-none"); - // Fetch data via AJAX - fetch(url, { - headers: { - "X-Requested-With": "XMLHttpRequest", - }, - }) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.status}`); - } - return response.json(); - }) - .then((data) => { - if (data.status === "success") { - // Update the content - contentContainer.innerHTML = data.html_data; + // Fetch data via AJAX + fetch(url, { + headers: { + "X-Requested-With": "XMLHttpRequest", + }, + }) + .then((response) => { + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + if (data.status === "success") { + // Update the content + contentContainer.innerHTML = data.html_data; - // Re-attach event listeners to new pagination links - setupPaginationListeners(); + // Re-attach event listeners to new pagination links + setupPaginationListeners(); - // Update any summary data if present and the page provides it - if (typeof updateSummary === "function" && data.summary) { - updateSummary(data); - } + // Update any summary data if present and the page provides it + if (typeof updateSummary === "function" && data.summary) { + updateSummary(data); + } - // Hide loading spinner, show content - loadingSpinner.classList.add("d-none"); - contentContainer.classList.remove("d-none"); + // Hide loading spinner, show content + loadingSpinner.classList.add("d-none"); + contentContainer.classList.remove("d-none"); - // Scroll to top of the content container - contentContainer.scrollIntoView({ behavior: "smooth", block: "start" }); - } - }) - .catch((error) => { - console.error("Error fetching data:", error); - loadingSpinner.classList.add("d-none"); - contentContainer.classList.remove("d-none"); - alert(config.retryMessage); - }); - } + // Scroll to top of the content container + contentContainer.scrollIntoView({ behavior: "smooth", block: "start" }); + } + }) + .catch((error) => { + console.error("Error fetching data:", error); + loadingSpinner.classList.add("d-none"); + contentContainer.classList.remove("d-none"); + alert(config.retryMessage); + }); + } - // Initial setup of event listeners - setupPaginationListeners(); + // Initial setup of event listeners + setupPaginationListeners(); - // Handle browser back/forward buttons - window.addEventListener("popstate", function (event) { - if (event.state && event.state.url) { - handleAjaxNavigation(event.state.url); - } else { - // If no state, fetch current URL - handleAjaxNavigation(window.location.href); - } - }); - } + // Handle browser back/forward buttons + window.addEventListener("popstate", function (event) { + if (event.state && event.state.url) { + handleAjaxNavigation(event.state.url); + } else { + // If no state, fetch current URL + handleAjaxNavigation(window.location.href); + } + }); + } }); diff --git a/dashboard_project/static/js/dashboard.js b/dashboard_project/static/js/dashboard.js index fe0d29c..2fc82d3 100644 --- a/dashboard_project/static/js/dashboard.js +++ b/dashboard_project/static/js/dashboard.js @@ -7,272 +7,272 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Chart responsiveness - function resizeCharts() { - const charts = document.querySelectorAll(".chart-container"); - charts.forEach((chart) => { - if (chart.id && window.Plotly) { - Plotly.relayout(chart.id, { - "xaxis.automargin": true, - "yaxis.automargin": true, - }); - } - }); - } + // Chart responsiveness + function resizeCharts() { + const charts = document.querySelectorAll(".chart-container"); + charts.forEach((chart) => { + if (chart.id && window.Plotly) { + Plotly.relayout(chart.id, { + "xaxis.automargin": true, + "yaxis.automargin": true, + }); + } + }); + } - // Handle window resize - window.addEventListener("resize", function () { - if (window.Plotly) { - resizeCharts(); - } - }); + // Handle window resize + window.addEventListener("resize", function () { + if (window.Plotly) { + resizeCharts(); + } + }); - // Time range filtering - const timeRangeDropdown = document.getElementById("timeRangeDropdown"); - if (timeRangeDropdown) { - const timeRangeLinks = timeRangeDropdown.querySelectorAll(".dropdown-item"); - timeRangeLinks.forEach((link) => { - link.addEventListener("click", function (e) { - const url = new URL(this.href); - const dashboardId = url.searchParams.get("dashboard_id"); - const timeRange = url.searchParams.get("time_range"); + // Time range filtering + const timeRangeDropdown = document.getElementById("timeRangeDropdown"); + if (timeRangeDropdown) { + const timeRangeLinks = timeRangeDropdown.querySelectorAll(".dropdown-item"); + timeRangeLinks.forEach((link) => { + link.addEventListener("click", function (e) { + const url = new URL(this.href); + const dashboardId = url.searchParams.get("dashboard_id"); + const timeRange = url.searchParams.get("time_range"); - // Fetch updated data via AJAX - if (dashboardId) { - fetchDashboardData(dashboardId, timeRange); - e.preventDefault(); - } - }); - }); - } + // Fetch updated data via AJAX + if (dashboardId) { + fetchDashboardData(dashboardId, timeRange); + e.preventDefault(); + } + }); + }); + } - // Function to fetch dashboard data - function fetchDashboardData(dashboardId, timeRange) { - const loadingOverlay = document.createElement("div"); - loadingOverlay.className = "loading-overlay"; - loadingOverlay.innerHTML = - '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>'; - document.querySelector("main").appendChild(loadingOverlay); + // Function to fetch dashboard data + function fetchDashboardData(dashboardId, timeRange) { + const loadingOverlay = document.createElement("div"); + loadingOverlay.className = "loading-overlay"; + loadingOverlay.innerHTML = + '<div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div>'; + document.querySelector("main").appendChild(loadingOverlay); - fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`) - .then((response) => { - if (!response.ok) { - throw new Error(`Network response was not ok: ${response.status}`); - } - return response.json(); - }) - .then((data) => { - console.log("Dashboard API response:", data); - updateDashboardStats(data); - updateDashboardCharts(data); + fetch(`/dashboard/api/dashboard/${dashboardId}/data/?time_range=${timeRange || "all"}`) + .then((response) => { + if (!response.ok) { + throw new Error(`Network response was not ok: ${response.status}`); + } + return response.json(); + }) + .then((data) => { + console.log("Dashboard API response:", data); + updateDashboardStats(data); + updateDashboardCharts(data); - // Update URL without page reload - const url = new URL(window.location.href); - url.searchParams.set("dashboard_id", dashboardId); - if (timeRange) { - url.searchParams.set("time_range", timeRange); - } - window.history.pushState({}, "", url); + // Update URL without page reload + const url = new URL(window.location.href); + url.searchParams.set("dashboard_id", dashboardId); + if (timeRange) { + url.searchParams.set("time_range", timeRange); + } + window.history.pushState({}, "", url); - document.querySelector(".loading-overlay").remove(); - }) - .catch((error) => { - console.error("Error fetching dashboard data:", error); - document.querySelector(".loading-overlay").remove(); + document.querySelector(".loading-overlay").remove(); + }) + .catch((error) => { + console.error("Error fetching dashboard data:", error); + document.querySelector(".loading-overlay").remove(); - // Show error message - const alertElement = document.createElement("div"); - alertElement.className = "alert alert-danger alert-dismissible fade show"; - alertElement.setAttribute("role", "alert"); - alertElement.innerHTML = ` + // Show error message + const alertElement = document.createElement("div"); + alertElement.className = "alert alert-danger alert-dismissible fade show"; + alertElement.setAttribute("role", "alert"); + alertElement.innerHTML = ` Error loading dashboard data. Please try again. <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button> `; - document.querySelector("main").prepend(alertElement); - }); - } + document.querySelector("main").prepend(alertElement); + }); + } - // Function to update dashboard statistics - function updateDashboardStats(data) { - // Update total sessions - const totalSessionsElement = document.querySelector(".stats-card:nth-child(1) h3"); - if (totalSessionsElement) { - totalSessionsElement.textContent = data.total_sessions; - } + // Function to update dashboard statistics + function updateDashboardStats(data) { + // Update total sessions + const totalSessionsElement = document.querySelector(".stats-card:nth-child(1) h3"); + if (totalSessionsElement) { + totalSessionsElement.textContent = data.total_sessions; + } - // Update average response time - const avgResponseTimeElement = document.querySelector(".stats-card:nth-child(2) h3"); - if (avgResponseTimeElement) { - avgResponseTimeElement.textContent = data.avg_response_time + "s"; - } + // Update average response time + const avgResponseTimeElement = document.querySelector(".stats-card:nth-child(2) h3"); + if (avgResponseTimeElement) { + avgResponseTimeElement.textContent = data.avg_response_time + "s"; + } - // Update total tokens - const totalTokensElement = document.querySelector(".stats-card:nth-child(3) h3"); - if (totalTokensElement) { - totalTokensElement.textContent = data.total_tokens; - } + // Update total tokens + const totalTokensElement = document.querySelector(".stats-card:nth-child(3) h3"); + if (totalTokensElement) { + totalTokensElement.textContent = data.total_tokens; + } - // Update total cost - const totalCostElement = document.querySelector(".stats-card:nth-child(4) h3"); - if (totalCostElement) { - totalCostElement.textContent = "€" + data.total_cost; - } - } + // Update total cost + const totalCostElement = document.querySelector(".stats-card:nth-child(4) h3"); + if (totalCostElement) { + totalCostElement.textContent = "€" + data.total_cost; + } + } - // Function to update dashboard charts - function updateDashboardCharts(data) { - // Check if Plotly is available - if (!window.Plotly) { - console.error("Plotly library not loaded!"); - document.querySelectorAll(".chart-container").forEach((container) => { - container.innerHTML = - '<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>'; - }); - return; - } + // Function to update dashboard charts + function updateDashboardCharts(data) { + // Check if Plotly is available + if (!window.Plotly) { + console.error("Plotly library not loaded!"); + document.querySelectorAll(".chart-container").forEach((container) => { + container.innerHTML = + '<div class="text-center py-5"><p class="text-danger">Chart library not available. Please refresh the page.</p></div>'; + }); + return; + } - // Update sessions over time chart - const timeSeriesData = data.time_series_data; - if (timeSeriesData && timeSeriesData.length > 0) { - try { - const timeSeriesX = timeSeriesData.map((item) => item.date); - const timeSeriesY = timeSeriesData.map((item) => item.count); + // Update sessions over time chart + const timeSeriesData = data.time_series_data; + if (timeSeriesData && timeSeriesData.length > 0) { + try { + const timeSeriesX = timeSeriesData.map((item) => item.date); + const timeSeriesY = timeSeriesData.map((item) => item.count); - Plotly.react( - "sessions-time-chart", - [ - { - x: timeSeriesX, - y: timeSeriesY, - type: "scatter", - mode: "lines+markers", - line: { - color: "rgb(75, 192, 192)", - width: 2, - }, - marker: { - color: "rgb(75, 192, 192)", - size: 6, - }, - }, - ], - { - margin: { t: 10, r: 10, b: 40, l: 40 }, - xaxis: { - title: "Date", - }, - yaxis: { - title: "Number of Sessions", - }, - } - ); - } catch (error) { - console.error("Error rendering time series chart:", error); - document.getElementById("sessions-time-chart").innerHTML = - '<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>'; - } - } else { - document.getElementById("sessions-time-chart").innerHTML = - '<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>'; - } + Plotly.react( + "sessions-time-chart", + [ + { + x: timeSeriesX, + y: timeSeriesY, + type: "scatter", + mode: "lines+markers", + line: { + color: "rgb(75, 192, 192)", + width: 2, + }, + marker: { + color: "rgb(75, 192, 192)", + size: 6, + }, + }, + ], + { + margin: { t: 10, r: 10, b: 40, l: 40 }, + xaxis: { + title: "Date", + }, + yaxis: { + title: "Number of Sessions", + }, + }, + ); + } catch (error) { + console.error("Error rendering time series chart:", error); + document.getElementById("sessions-time-chart").innerHTML = + '<div class="text-center py-5"><p class="text-danger">Error rendering chart.</p></div>'; + } + } else { + document.getElementById("sessions-time-chart").innerHTML = + '<div class="text-center py-5"><p class="text-muted">No time series data available</p></div>'; + } - // Update sentiment chart - const sentimentData = data.sentiment_data; - if (sentimentData && sentimentData.length > 0 && window.Plotly) { - const sentimentLabels = sentimentData.map((item) => item.sentiment); - const sentimentValues = sentimentData.map((item) => item.count); - const sentimentColors = sentimentLabels.map((sentiment) => { - if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)"; - if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)"; - if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)"; - return "rgb(201, 203, 207)"; - }); + // Update sentiment chart + const sentimentData = data.sentiment_data; + if (sentimentData && sentimentData.length > 0 && window.Plotly) { + const sentimentLabels = sentimentData.map((item) => item.sentiment); + const sentimentValues = sentimentData.map((item) => item.count); + const sentimentColors = sentimentLabels.map((sentiment) => { + if (sentiment.toLowerCase().includes("positive")) return "rgb(75, 192, 92)"; + if (sentiment.toLowerCase().includes("negative")) return "rgb(255, 99, 132)"; + if (sentiment.toLowerCase().includes("neutral")) return "rgb(255, 205, 86)"; + return "rgb(201, 203, 207)"; + }); - Plotly.react( - "sentiment-chart", - [ - { - values: sentimentValues, - labels: sentimentLabels, - type: "pie", - marker: { - colors: sentimentColors, - }, - hole: 0.4, - textinfo: "label+percent", - insidetextorientation: "radial", - }, - ], - { - margin: { t: 10, r: 10, b: 10, l: 10 }, - } - ); - } + Plotly.react( + "sentiment-chart", + [ + { + values: sentimentValues, + labels: sentimentLabels, + type: "pie", + marker: { + colors: sentimentColors, + }, + hole: 0.4, + textinfo: "label+percent", + insidetextorientation: "radial", + }, + ], + { + margin: { t: 10, r: 10, b: 10, l: 10 }, + }, + ); + } - // Update country chart - const countryData = data.country_data; - if (countryData && countryData.length > 0 && window.Plotly) { - const countryLabels = countryData.map((item) => item.country); - const countryValues = countryData.map((item) => item.count); + // Update country chart + const countryData = data.country_data; + if (countryData && countryData.length > 0 && window.Plotly) { + const countryLabels = countryData.map((item) => item.country); + const countryValues = countryData.map((item) => item.count); - Plotly.react( - "country-chart", - [ - { - x: countryValues, - y: countryLabels, - type: "bar", - orientation: "h", - marker: { - color: "rgb(54, 162, 235)", - }, - }, - ], - { - margin: { t: 10, r: 10, b: 40, l: 100 }, - xaxis: { - title: "Number of Sessions", - }, - } - ); - } + Plotly.react( + "country-chart", + [ + { + x: countryValues, + y: countryLabels, + type: "bar", + orientation: "h", + marker: { + color: "rgb(54, 162, 235)", + }, + }, + ], + { + margin: { t: 10, r: 10, b: 40, l: 100 }, + xaxis: { + title: "Number of Sessions", + }, + }, + ); + } - // Update category chart - const categoryData = data.category_data; - if (categoryData && categoryData.length > 0 && window.Plotly) { - const categoryLabels = categoryData.map((item) => item.category); - const categoryValues = categoryData.map((item) => item.count); + // Update category chart + const categoryData = data.category_data; + if (categoryData && categoryData.length > 0 && window.Plotly) { + const categoryLabels = categoryData.map((item) => item.category); + const categoryValues = categoryData.map((item) => item.count); - Plotly.react( - "category-chart", - [ - { - labels: categoryLabels, - values: categoryValues, - type: "pie", - textinfo: "label+percent", - insidetextorientation: "radial", - }, - ], - { - margin: { t: 10, r: 10, b: 10, l: 10 }, - } - ); - } - } + Plotly.react( + "category-chart", + [ + { + labels: categoryLabels, + values: categoryValues, + type: "pie", + textinfo: "label+percent", + insidetextorientation: "radial", + }, + ], + { + margin: { t: 10, r: 10, b: 10, l: 10 }, + }, + ); + } + } - // Dashboard selector - const dashboardSelector = document.querySelectorAll('a[href^="?dashboard_id="]'); - dashboardSelector.forEach((link) => { - link.addEventListener("click", function (e) { - const url = new URL(this.href); - const dashboardId = url.searchParams.get("dashboard_id"); + // Dashboard selector + const dashboardSelector = document.querySelectorAll('a[href^="?dashboard_id="]'); + dashboardSelector.forEach((link) => { + link.addEventListener("click", function (e) { + const url = new URL(this.href); + const dashboardId = url.searchParams.get("dashboard_id"); - // Fetch updated data via AJAX - if (dashboardId) { - fetchDashboardData(dashboardId); - e.preventDefault(); - } - }); - }); + // Fetch updated data via AJAX + if (dashboardId) { + fetchDashboardData(dashboardId); + e.preventDefault(); + } + }); + }); }); diff --git a/dashboard_project/static/js/main.js b/dashboard_project/static/js/main.js index f2e31c9..e8a0d5d 100644 --- a/dashboard_project/static/js/main.js +++ b/dashboard_project/static/js/main.js @@ -6,147 +6,147 @@ */ document.addEventListener("DOMContentLoaded", function () { - // Initialize tooltips - var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); - var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl); - }); + // Initialize tooltips + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + return new bootstrap.Tooltip(tooltipTriggerEl); + }); - // Initialize popovers - var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); - var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { - return new bootstrap.Popover(popoverTriggerEl); - }); + // Initialize popovers + var popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); + var popoverList = popoverTriggerList.map(function (popoverTriggerEl) { + return new bootstrap.Popover(popoverTriggerEl); + }); - // Toggle sidebar on mobile - const sidebarToggle = document.querySelector("#sidebarToggle"); - if (sidebarToggle) { - sidebarToggle.addEventListener("click", function () { - document.querySelector(".sidebar").classList.toggle("show"); - }); - } + // Toggle sidebar on mobile + const sidebarToggle = document.querySelector("#sidebarToggle"); + if (sidebarToggle) { + sidebarToggle.addEventListener("click", function () { + document.querySelector(".sidebar").classList.toggle("show"); + }); + } - // Auto-dismiss alerts after 5 seconds - setTimeout(function () { - var alerts = document.querySelectorAll(".alert:not(.alert-important)"); - alerts.forEach(function (alert) { - if (alert && bootstrap.Alert.getInstance(alert)) { - bootstrap.Alert.getInstance(alert).close(); - } - }); - }, 5000); + // Auto-dismiss alerts after 5 seconds + setTimeout(function () { + var alerts = document.querySelectorAll(".alert:not(.alert-important)"); + alerts.forEach(function (alert) { + if (alert && bootstrap.Alert.getInstance(alert)) { + bootstrap.Alert.getInstance(alert).close(); + } + }); + }, 5000); - // Form validation - const forms = document.querySelectorAll(".needs-validation"); - forms.forEach(function (form) { - form.addEventListener( - "submit", - function (event) { - if (!form.checkValidity()) { - event.preventDefault(); - event.stopPropagation(); - } - form.classList.add("was-validated"); - }, - false - ); - }); + // Form validation + const forms = document.querySelectorAll(".needs-validation"); + forms.forEach(function (form) { + form.addEventListener( + "submit", + function (event) { + if (!form.checkValidity()) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add("was-validated"); + }, + false, + ); + }); - // Confirm dialogs - const confirmButtons = document.querySelectorAll("[data-confirm]"); - confirmButtons.forEach(function (button) { - button.addEventListener("click", function (event) { - if (!confirm(this.dataset.confirm || "Are you sure?")) { - event.preventDefault(); - } - }); - }); + // Confirm dialogs + const confirmButtons = document.querySelectorAll("[data-confirm]"); + confirmButtons.forEach(function (button) { + button.addEventListener("click", function (event) { + if (!confirm(this.dataset.confirm || "Are you sure?")) { + event.preventDefault(); + } + }); + }); - // Back button - const backButtons = document.querySelectorAll(".btn-back"); - backButtons.forEach(function (button) { - button.addEventListener("click", function (event) { - event.preventDefault(); - window.history.back(); - }); - }); + // Back button + const backButtons = document.querySelectorAll(".btn-back"); + backButtons.forEach(function (button) { + button.addEventListener("click", function (event) { + event.preventDefault(); + window.history.back(); + }); + }); - // File input customization - const fileInputs = document.querySelectorAll(".custom-file-input"); - fileInputs.forEach(function (input) { - input.addEventListener("change", function (e) { - const fileName = this.files[0]?.name || "Choose file"; - const nextSibling = this.nextElementSibling; - if (nextSibling) { - nextSibling.innerText = fileName; - } - }); - }); + // File input customization + const fileInputs = document.querySelectorAll(".custom-file-input"); + fileInputs.forEach(function (input) { + input.addEventListener("change", function (e) { + const fileName = this.files[0]?.name || "Choose file"; + const nextSibling = this.nextElementSibling; + if (nextSibling) { + nextSibling.innerText = fileName; + } + }); + }); - // Search form submit on enter - const searchInputs = document.querySelectorAll(".search-input"); - searchInputs.forEach(function (input) { - input.addEventListener("keypress", function (e) { - if (e.key === "Enter") { - e.preventDefault(); - this.closest("form").submit(); - } - }); - }); + // Search form submit on enter + const searchInputs = document.querySelectorAll(".search-input"); + searchInputs.forEach(function (input) { + input.addEventListener("keypress", function (e) { + if (e.key === "Enter") { + e.preventDefault(); + this.closest("form").submit(); + } + }); + }); - // Toggle password visibility - const togglePasswordButtons = document.querySelectorAll(".toggle-password"); - togglePasswordButtons.forEach(function (button) { - button.addEventListener("click", function () { - const target = document.querySelector(this.dataset.target); - if (target) { - const type = target.getAttribute("type") === "password" ? "text" : "password"; - target.setAttribute("type", type); - this.querySelector("i").classList.toggle("fa-eye"); - this.querySelector("i").classList.toggle("fa-eye-slash"); - } - }); - }); + // Toggle password visibility + const togglePasswordButtons = document.querySelectorAll(".toggle-password"); + togglePasswordButtons.forEach(function (button) { + button.addEventListener("click", function () { + const target = document.querySelector(this.dataset.target); + if (target) { + const type = target.getAttribute("type") === "password" ? "text" : "password"; + target.setAttribute("type", type); + this.querySelector("i").classList.toggle("fa-eye"); + this.querySelector("i").classList.toggle("fa-eye-slash"); + } + }); + }); - // Dropdown menu positioning - const dropdowns = document.querySelectorAll(".dropdown-menu"); - dropdowns.forEach(function (dropdown) { - dropdown.addEventListener("click", function (e) { - e.stopPropagation(); - }); - }); + // Dropdown menu positioning + const dropdowns = document.querySelectorAll(".dropdown-menu"); + dropdowns.forEach(function (dropdown) { + dropdown.addEventListener("click", function (e) { + e.stopPropagation(); + }); + }); - // Responsive table handling - const tables = document.querySelectorAll(".table-responsive"); - if (window.innerWidth < 768) { - tables.forEach(function (table) { - table.classList.add("table-responsive-force"); - }); - } + // Responsive table handling + const tables = document.querySelectorAll(".table-responsive"); + if (window.innerWidth < 768) { + tables.forEach(function (table) { + table.classList.add("table-responsive-force"); + }); + } - // Handle special links (printable views, exports) - const printLinks = document.querySelectorAll(".print-link"); - printLinks.forEach(function (link) { - link.addEventListener("click", function (e) { - e.preventDefault(); - window.print(); - }); - }); + // Handle special links (printable views, exports) + const printLinks = document.querySelectorAll(".print-link"); + printLinks.forEach(function (link) { + link.addEventListener("click", function (e) { + e.preventDefault(); + window.print(); + }); + }); - const exportLinks = document.querySelectorAll("[data-export]"); - exportLinks.forEach(function (link) { - link.addEventListener("click", function (e) { - // Handle export functionality if needed - console.log("Export requested:", this.dataset.export); - }); - }); + const exportLinks = document.querySelectorAll("[data-export]"); + exportLinks.forEach(function (link) { + link.addEventListener("click", function (e) { + // Handle export functionality if needed + console.log("Export requested:", this.dataset.export); + }); + }); - // Handle sidebar collapse on small screens - function handleSidebarOnResize() { - if (window.innerWidth < 768) { - document.querySelector(".sidebar")?.classList.remove("show"); - } - } + // Handle sidebar collapse on small screens + function handleSidebarOnResize() { + if (window.innerWidth < 768) { + document.querySelector(".sidebar")?.classList.remove("show"); + } + } - window.addEventListener("resize", handleSidebarOnResize); + window.addEventListener("resize", handleSidebarOnResize); }); diff --git a/dashboard_project/templates/accounts/login.html b/dashboard_project/templates/accounts/login.html index 6a153d2..450e4a8 100644 --- a/dashboard_project/templates/accounts/login.html +++ b/dashboard_project/templates/accounts/login.html @@ -5,25 +5,27 @@ {% block title %}Login | Chat Analytics{% endblock %} {% block content %} - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card mt-4"> - <div class="card-header"> - <h4 class="card-title mb-0">Login</h4> - </div> - <div class="card-body"> - <form method="post"> - {% csrf_token %} - {{ form|crispy }} - <div class="d-grid gap-2"> - <button type="submit" class="btn btn-primary">Login</button> - </div> - </form> - </div> - <div class="card-footer text-center"> - <p class="mb-0">Don't have an account? <a href="{% url 'register' %}">Register</a></p> - </div> - </div> - </div> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card mt-4"> + <div class="card-header"> + <h4 class="card-title mb-0">Login</h4> + </div> + <div class="card-body"> + <form method="post"> + {% csrf_token %} + {{ form|crispy }} + <div class="d-grid gap-2"> + <button type="submit" class="btn btn-primary">Login</button> + </div> + </form> + </div> + <div class="card-footer text-center"> + <p class="mb-0"> + Don't have an account? <a href="{% url 'register' %}">Register</a> + </p> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/password_change.html b/dashboard_project/templates/accounts/password_change.html index 47b7131..9228cc3 100644 --- a/dashboard_project/templates/accounts/password_change.html +++ b/dashboard_project/templates/accounts/password_change.html @@ -5,33 +5,33 @@ {% block title %}Change Password | Chat Analytics{% endblock %} {% block content %} - <div - class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" - > - <h1 class="h2">Change Password</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Profile - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">Change Password</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Profile + </a> + </div> + </div> - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Change Your Password</h5> - </div> - <div class="card-body"> - <form method="post"> - {% csrf_token %} - {{ form|crispy }} - <div class="d-grid gap-2"> - <button type="submit" class="btn btn-primary">Change Password</button> - </div> - </form> - </div> - </div> - </div> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Change Your Password</h5> + </div> + <div class="card-body"> + <form method="post"> + {% csrf_token %} + {{ form|crispy }} + <div class="d-grid gap-2"> + <button type="submit" class="btn btn-primary">Change Password</button> + </div> + </form> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/password_change_done.html b/dashboard_project/templates/accounts/password_change_done.html index 28c2edd..1a8c441 100644 --- a/dashboard_project/templates/accounts/password_change_done.html +++ b/dashboard_project/templates/accounts/password_change_done.html @@ -4,38 +4,41 @@ {% block title %}Password Changed | Chat Analytics{% endblock %} {% block content %} - <div - class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" - > - <h1 class="h2">Password Changed</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Profile - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">Password Changed</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'profile' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Profile + </a> + </div> + </div> - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header bg-success text-white"> - <h5 class="card-title mb-0">Password Changed Successfully</h5> - </div> - <div class="card-body text-center"> - <div class="mb-4"> - <i class="fas fa-check-circle fa-4x text-success mb-3"></i> - <h4>Your password has been changed successfully!</h4> - <p>Your new password is now active. You can use it the next time you log in.</p> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header bg-success text-white"> + <h5 class="card-title mb-0">Password Changed Successfully</h5> + </div> + <div class="card-body text-center"> + <div class="mb-4"> + <i class="fas fa-check-circle fa-4x text-success mb-3"></i> + <h4>Your password has been changed successfully!</h4> + <p> + Your new password is now active. You can use it the next time you log + in. + </p> + </div> - <div class="mt-4"> - <a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a> - <a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2" - >Go to Dashboard</a - > - </div> - </div> - </div> - </div> - </div> + <div class="mt-4"> + <a href="{% url 'profile' %}" class="btn btn-primary">Return to Profile</a> + <a href="{% url 'dashboard' %}" class="btn btn-outline-secondary ms-2" + >Go to Dashboard</a + > + </div> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/accounts/profile.html b/dashboard_project/templates/accounts/profile.html index 389b0b5..69422b1 100644 --- a/dashboard_project/templates/accounts/profile.html +++ b/dashboard_project/templates/accounts/profile.html @@ -4,186 +4,210 @@ {% block title %}My Profile | Chat Analytics{% endblock %} {% block content %} - <div - class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" - > - <h1 class="h2">My Profile</h1> - <div class="btn-toolbar mb-2 mb-md-0"> - <a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary"> - <i class="fas fa-arrow-left"></i> Back to Dashboard - </a> - </div> - </div> + <div + class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom" + > + <h1 class="h2">My Profile</h1> + <div class="btn-toolbar mb-2 mb-md-0"> + <a href="{% url 'dashboard' %}" class="btn btn-sm btn-outline-secondary"> + <i class="fas fa-arrow-left"></i> Back to Dashboard + </a> + </div> + </div> - <div class="row"> - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Account Information</h5> - </div> - <div class="card-body"> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Username:</div> - <div class="col-md-8">{{ user.username }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Email:</div> - <div class="col-md-8">{{ user.email }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Company:</div> - <div class="col-md-8"> - {% if user.company %} - {{ user.company.name }} - {% else %} - <span class="text-muted">Not assigned to a company</span> - {% endif %} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Role:</div> - <div class="col-md-8"> - {% if user.is_staff %} - <span class="badge bg-danger">Admin</span> - {% elif user.is_company_admin %} - <span class="badge bg-primary">Company Admin</span> - {% else %} - <span class="badge bg-secondary">User</span> - {% endif %} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Last Login:</div> - <div class="col-md-8">{{ user.last_login|date:"F d, Y H:i" }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Date Joined:</div> - <div class="col-md-8">{{ user.date_joined|date:"F d, Y H:i" }}</div> - </div> - </div> - <div class="card-footer"> - <a href="{% url 'password_change' %}" class="btn btn-primary">Change Password</a> - </div> - </div> - </div> + <div class="row"> + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Account Information</h5> + </div> + <div class="card-body"> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Username:</div> + <div class="col-md-8">{{ user.username }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Email:</div> + <div class="col-md-8">{{ user.email }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Company:</div> + <div class="col-md-8"> + {% if user.company %} + {{ user.company.name }} + {% else %} + <span class="text-muted">Not assigned to a company</span> + {% endif %} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Role:</div> + <div class="col-md-8"> + {% if user.is_staff %} + <span class="badge bg-danger">Admin</span> + {% elif user.is_company_admin %} + <span class="badge bg-primary">Company Admin</span> + {% else %} + <span class="badge bg-secondary">User</span> + {% endif %} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Last Login:</div> + <div class="col-md-8">{{ user.last_login|date:"F d, Y H:i" }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Date Joined:</div> + <div class="col-md-8">{{ user.date_joined|date:"F d, Y H:i" }}</div> + </div> + </div> + <div class="card-footer"> + <a href="{% url 'password_change' %}" class="btn btn-primary" + >Change Password</a + > + </div> + </div> + </div> - {% if user.company %} - <div class="col-md-6"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Company Information</h5> - </div> - <div class="card-body"> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Company Name:</div> - <div class="col-md-8">{{ user.company.name }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Description:</div> - <div class="col-md-8"> - {{ user.company.description|default:"No description available." }} - </div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Created:</div> - <div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Total Employees:</div> - <div class="col-md-8">{{ user.company.employees.count }}</div> - </div> - <div class="row mb-3"> - <div class="col-md-4 fw-bold">Data Sources:</div> - <div class="col-md-8">{{ user.company.data_sources.count }}</div> - </div> - </div> - </div> - </div> - {% endif %} - </div> + {% if user.company %} + <div class="col-md-6"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Company Information</h5> + </div> + <div class="card-body"> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Company Name:</div> + <div class="col-md-8">{{ user.company.name }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Description:</div> + <div class="col-md-8"> + {{ user.company.description|default:"No description available." }} + </div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Created:</div> + <div class="col-md-8">{{ user.company.created_at|date:"F d, Y" }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Total Employees:</div> + <div class="col-md-8">{{ user.company.employees.count }}</div> + </div> + <div class="row mb-3"> + <div class="col-md-4 fw-bold">Data Sources:</div> + <div class="col-md-8">{{ user.company.data_sources.count }}</div> + </div> + </div> + </div> + </div> + {% endif %} + </div> - {% if user.is_company_admin or user.is_staff %} - <div class="row mt-4"> - <div class="col-12"> - <div class="card"> - <div class="card-header"> - <h5 class="card-title mb-0">Admin Actions</h5> - </div> - <div class="card-body"> - <div class="row"> - {% if user.is_staff %} - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Manage Users</h5> - <p class="card-text">Manage users and assign them to companies.</p> - <a - href="{% url 'admin:accounts_customuser_changelist' %}" - class="btn btn-primary" - >Manage Users</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Manage Companies</h5> - <p class="card-text">Create and edit companies in the system.</p> - <a - href="{% url 'admin:accounts_company_changelist' %}" - class="btn btn-primary" - >Manage Companies</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Admin Dashboard</h5> - <p class="card-text">Go to the full admin dashboard.</p> - <a href="{% url 'admin:index' %}" class="btn btn-primary">Admin Dashboard</a> - </div> - </div> - </div> - {% elif user.is_company_admin %} - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Manage Dashboards</h5> - <p class="card-text">Create and edit dashboards for your company.</p> - <a href="{% url 'create_dashboard' %}" class="btn btn-primary" - >Manage Dashboards</a - > - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Upload Data</h5> - <p class="card-text">Upload and manage data sources for analysis.</p> - <a href="{% url 'upload_data' %}" class="btn btn-primary">Upload Data</a> - </div> - </div> - </div> - <div class="col-md-4 mb-3"> - <div class="card h-100"> - <div class="card-body text-center"> - <h5 class="card-title">Search Sessions</h5> - <p class="card-text">Search and analyze chat sessions.</p> - <a href="{% url 'search_chat_sessions' %}" class="btn btn-primary" - >Search Sessions</a - > - </div> - </div> - </div> - {% endif %} - </div> - </div> - </div> - </div> - </div> - {% endif %} + {% if user.is_company_admin or user.is_staff %} + <div class="row mt-4"> + <div class="col-12"> + <div class="card"> + <div class="card-header"> + <h5 class="card-title mb-0">Admin Actions</h5> + </div> + <div class="card-body"> + <div class="row"> + {% if user.is_staff %} + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Manage Users</h5> + <p class="card-text"> + Manage users and assign them to companies. + </p> + <a + href="{% url 'admin:accounts_customuser_changelist' %}" + class="btn btn-primary" + >Manage Users</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Manage Companies</h5> + <p class="card-text"> + Create and edit companies in the system. + </p> + <a + href="{% url 'admin:accounts_company_changelist' %}" + class="btn btn-primary" + >Manage Companies</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Admin Dashboard</h5> + <p class="card-text">Go to the full admin dashboard.</p> + <a + href="{% url 'admin:index' %}" + class="btn btn-primary" + >Admin Dashboard</a + > + </div> + </div> + </div> + {% elif user.is_company_admin %} + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Manage Dashboards</h5> + <p class="card-text"> + Create and edit dashboards for your company. + </p> + <a + href="{% url 'create_dashboard' %}" + class="btn btn-primary" + >Manage Dashboards</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Upload Data</h5> + <p class="card-text"> + Upload and manage data sources for analysis. + </p> + <a + href="{% url 'upload_data' %}" + class="btn btn-primary" + >Upload Data</a + > + </div> + </div> + </div> + <div class="col-md-4 mb-3"> + <div class="card h-100"> + <div class="card-body text-center"> + <h5 class="card-title">Search Sessions</h5> + <p class="card-text"> + Search and analyze chat sessions. + </p> + <a + href="{% url 'search_chat_sessions' %}" + class="btn btn-primary" + >Search Sessions</a + > + </div> + </div> + </div> + {% endif %} + </div> + </div> + </div> + </div> + </div> + {% endif %} {% endblock %} diff --git a/dashboard_project/templates/accounts/register.html b/dashboard_project/templates/accounts/register.html index 72ff9b3..cc85a85 100644 --- a/dashboard_project/templates/accounts/register.html +++ b/dashboard_project/templates/accounts/register.html @@ -5,25 +5,27 @@ {% block title %}Register | Chat Analytics{% endblock %} {% block content %} - <div class="row justify-content-center"> - <div class="col-md-6"> - <div class="card mt-4"> - <div class="card-header"> - <h4 class="card-title mb-0">Register</h4> - </div> - <div class="card-body"> - <form method="post"> - {% csrf_token %} - {{ form|crispy }} - <div class="d-grid gap-2"> - <button type="submit" class="btn btn-primary">Register</button> - </div> - </form> - </div> - <div class="card-footer text-center"> - <p class="mb-0">Already have an account? <a href="{% url 'login' %}">Login</a></p> - </div> - </div> - </div> - </div> + <div class="row justify-content-center"> + <div class="col-md-6"> + <div class="card mt-4"> + <div class="card-header"> + <h4 class="card-title mb-0">Register</h4> + </div> + <div class="card-body"> + <form method="post"> + {% csrf_token %} + {{ form|crispy }} + <div class="d-grid gap-2"> + <button type="submit" class="btn btn-primary">Register</button> + </div> + </form> + </div> + <div class="card-footer text-center"> + <p class="mb-0"> + Already have an account? <a href="{% url 'login' %}">Login</a> + </p> + </div> + </div> + </div> + </div> {% endblock %} diff --git a/dashboard_project/templates/base.html b/dashboard_project/templates/base.html index 7fae940..1545d34 100644 --- a/dashboard_project/templates/base.html +++ b/dashboard_project/templates/base.html @@ -2,314 +2,339 @@ {% load static %} <!doctype html> <html lang="en"> - <head> - <meta charset="UTF-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>{% block title %}Chat Analytics Dashboard{% endblock %} + + + + {% block title %}Chat Analytics Dashboard{% endblock %} - - + + - - + + - - + + - - - + + + - {% block extra_css %}{% endblock %} - + {% block extra_css %}{% endblock %} + - - - -
-
- - +
+
+ + - -
- {# {% if messages %} #} - {#
#} - {# {% for message in messages %} #} - {# #} - {# {% endfor %} #} - {#
#} - {# {% endif %} #} + +
+ {# {% if messages %} #} + {#
#} + {# {% for message in messages %} #} + {# #} + {# {% endfor %} #} + {#
#} + {# {% endif %} #} -
- {% block content %} - {% endblock %} -
-
-
-
+
+ {% block content %} + {% endblock %} +
+ +
+
-
-
-

© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.

-
-
+
+
+

© {% now "Y" %} KJANAT All rights reserved. | Chat Analytics Dashboard.

+
+
- - - - + + + + - - - - + + + + - - + + - - + + - {% block extra_js %} - {{ block.super }} - {% if messages %} -
- -
+ {% block extra_js %} + {{ block.super }} + {% if messages %} +
+ +
- {% for message in messages %} - - - {% endfor %} + {% for message in messages %} + + + {% endfor %} - - {% endif %} - {% endblock %} - + toast.show(); + } + } + }); + + {% endif %} + {% endblock %} + diff --git a/dashboard_project/templates/dashboard/chat_session_detail.html b/dashboard_project/templates/dashboard/chat_session_detail.html index b3ae271..2661ad5 100644 --- a/dashboard_project/templates/dashboard/chat_session_detail.html +++ b/dashboard_project/templates/dashboard/chat_session_detail.html @@ -3,128 +3,143 @@ {% block title %}Chat Session {{ session.session_id }} | Chat Analytics{% endblock %} {% block content %} -
-

Chat Session: {{ session.session_id }}

- -
+
+

Chat Session: {{ session.session_id }}

+ +
-
-
-
-
-
Session Information
-
-
-
-
-

Session ID: {{ session.session_id }}

-

Start Time: {{ session.start_time|date:"F d, Y H:i" }}

-

End Time: {{ session.end_time|date:"F d, Y H:i" }}

-

IP Address: {{ session.ip_address|default:"N/A" }}

-

Country: {{ session.country|default:"N/A" }}

-

Language: {{ session.language|default:"N/A" }}

-
-
-

Messages Sent: {{ session.messages_sent }}

-

- Average Response Time: - {{ session.avg_response_time|floatformat:2 }}s -

-

Tokens: {{ session.tokens }}

-

Token Cost: €{{ session.tokens_eur|floatformat:2 }}

-

Category: {{ session.category|default:"N/A" }}

-

- Sentiment: - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} -

-
-
-
-
-

Initial Message:

-
-
-

{{ session.initial_msg|default:"N/A" }}

-
-
-
-
-
-
-
-
-
-
-
Additional Info
-
-
-

- Escalated: {% if session.escalated %} - Yes - {% else %} - No - {% endif %} -

-

- Forwarded to HR: {% if session.forwarded_hr %} - Yes - {% else %} - No - {% endif %} -

-

User Rating: {{ session.user_rating|default:"N/A" }}

-
-

- Data Source: - {{ session.data_source.name }} -

-

Company: {{ session.data_source.company.name }}

-
-
-
-
+
+
+
+
+
Session Information
+
+
+
+
+

Session ID: {{ session.session_id }}

+

+ Start Time: + {{ session.start_time|date:"F d, Y H:i" }} +

+

+ End Time: {{ session.end_time|date:"F d, Y H:i" }} +

+

+ IP Address: {{ session.ip_address|default:"N/A" }} +

+

Country: {{ session.country|default:"N/A" }}

+

Language: {{ session.language|default:"N/A" }}

+
+
+

Messages Sent: {{ session.messages_sent }}

+

+ Average Response Time: + {{ session.avg_response_time|floatformat:2 }}s +

+

Tokens: {{ session.tokens }}

+

+ Token Cost: €{{ session.tokens_eur|floatformat:2 }} +

+

Category: {{ session.category|default:"N/A" }}

+

+ Sentiment: + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} +

+
+
+
+
+

Initial Message:

+
+
+

{{ session.initial_msg|default:"N/A" }}

+
+
+
+
+
+
+
+
+
+
+
Additional Info
+
+
+

+ Escalated: {% if session.escalated %} + Yes + {% else %} + No + {% endif %} +

+

+ Forwarded to HR: {% if session.forwarded_hr %} + Yes + {% else %} + No + {% endif %} +

+

User Rating: {{ session.user_rating|default:"N/A" }}

+
+

+ Data Source: + {{ session.data_source.name }} +

+

Company: {{ session.data_source.company.name }}

+
+
+
+
-
-
-
-
-
Full Transcript
-
-
- {% if session.full_transcript %} -
-
+	
+
+
+
+
Full Transcript
+
+
+ {% if session.full_transcript %} +
+
 {{ session.full_transcript }}
-
- {% else %} -

No transcript available.

- {% endif %} -
-
-
-
+ > +
+ {% else %} +

No transcript available.

+ {% endif %} +
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard.html b/dashboard_project/templates/dashboard/dashboard.html index 3ff364f..575ed6e 100644 --- a/dashboard_project/templates/dashboard/dashboard.html +++ b/dashboard_project/templates/dashboard/dashboard.html @@ -5,155 +5,163 @@ {% block title %}Dashboard | Chat Analytics{% endblock %} {% block content %} -
-

{{ selected_dashboard.name }}

-
- - -
-
+
+

{{ selected_dashboard.name }}

+
+ + +
+
-
-
-
-
-
Total Sessions
-

{{ dashboard_data.total_sessions }}

-

Chat conversations

-
-
-
-
-
-
-
Avg Response Time
-

{{ dashboard_data.avg_response_time }}s

-

Average response

-
-
-
-
-
-
-
Total Tokens
-

{{ dashboard_data.total_tokens }}

-

Total usage

-
-
-
-
-
-
-
Total Cost
-

€{{ dashboard_data.total_cost }}

-

Token cost

-
-
-
-
+
+
+
+
+
Total Sessions
+

{{ dashboard_data.total_sessions }}

+

Chat conversations

+
+
+
+
+
+
+
Avg Response Time
+

{{ dashboard_data.avg_response_time }}s

+

Average response

+
+
+
+
+
+
+
Total Tokens
+

{{ dashboard_data.total_tokens }}

+

Total usage

+
+
+
+
+
+
+
Total Cost
+

€{{ dashboard_data.total_cost }}

+

Token cost

+
+
+
+
-
-
-
-
-
Sessions Over Time
-
-
-
-
-
-
-
-
-
-
Sentiment Analysis
-
-
-
-
-
-
-
+
+
+
+
+
Sessions Over Time
+
+
+
+
+
+
+
+
+
+
Sentiment Analysis
+
+
+
+
+
+
+
-
-
-
-
-
Top Countries
-
-
-
-
-
-
-
-
-
-
Categories
-
-
-
-
-
-
-
+
+
+
+
+
Top Countries
+
+
+
+
+
+
+
+
+
+
Categories
+
+
+
+
+
+
+
{% endblock %} {% block extra_js %} - + @@ -161,154 +169,161 @@ - + Plotly.newPlot( + "category-chart", + [ + { + labels: categoryLabels, + values: categoryValues, + type: "pie", + textinfo: "label+percent", + insidetextorientation: "radial", + }, + ], + { + margin: { t: 10, r: 10, b: 10, l: 10 }, + }, + ); + } else { + document.getElementById("category-chart").innerHTML = + '

No category data available

'; + } + } catch (error) { + console.error("Error rendering charts:", error); + document.querySelectorAll(".chart-container").forEach((container) => { + container.innerHTML = + '

Error loading chart data. Please refresh the page.

'; + }); + } + }); + {% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard_confirm_delete.html b/dashboard_project/templates/dashboard/dashboard_confirm_delete.html index 29d7f70..c407162 100644 --- a/dashboard_project/templates/dashboard/dashboard_confirm_delete.html +++ b/dashboard_project/templates/dashboard/dashboard_confirm_delete.html @@ -3,41 +3,42 @@ {% block title %}Delete Dashboard | Chat Analytics{% endblock %} {% block content %} -
-

Delete Dashboard

- -
+
+

Delete Dashboard

+ +
-
-
-
-
-
Confirm Deletion
-
-
-

- Are you sure you want to delete the dashboard "{{ dashboard.name }}"? -

-

- This action cannot be undone. The dashboard will be permanently deleted, but the - underlying data sources will remain intact. -

+
+
+
+
+
Confirm Deletion
+
+
+

+ Are you sure you want to delete the dashboard + "{{ dashboard.name }}"? +

+

+ This action cannot be undone. The dashboard will be permanently deleted, but + the underlying data sources will remain intact. +

-
- {% csrf_token %} -
- Cancel - -
-
-
-
-
-
+
+ {% csrf_token %} +
+ Cancel + +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/dashboard_form.html b/dashboard_project/templates/dashboard/dashboard_form.html index 46eb542..46e3860 100644 --- a/dashboard_project/templates/dashboard/dashboard_form.html +++ b/dashboard_project/templates/dashboard/dashboard_form.html @@ -2,42 +2,42 @@ {% load crispy_forms_tags %} {% block title %} - {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} - | Chat Analytics + {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} + | Chat Analytics {% endblock %} {% block content %} -
-

{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}

- -
+
+

{% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %}

+ +
-
-
-
-
-
- {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} -
-
-
-
- {% csrf_token %} - {{ form|crispy }} -
- -
-
-
-
-
-
+
+
+
+
+
+ {% if is_create %}Create Dashboard{% else %}Edit Dashboard{% endif %} +
+
+
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/data_source_confirm_delete.html b/dashboard_project/templates/dashboard/data_source_confirm_delete.html index de6b50a..21a69f0 100644 --- a/dashboard_project/templates/dashboard/data_source_confirm_delete.html +++ b/dashboard_project/templates/dashboard/data_source_confirm_delete.html @@ -4,47 +4,50 @@ {% block title %}Delete Data Source | Chat Analytics{% endblock %} {% block content %} -
-

Delete Data Source

- -
+
+

Delete Data Source

+ +
-
-
-
-
-
Confirm Deletion
-
-
-

- Are you sure you want to delete the data source - "{{ data_source.name }}"? -

-

- This action cannot be undone. The data source and all associated chat sessions - ({{ data_source.chat_sessions.count }} sessions) will be permanently deleted. -

+
+
+
+
+
Confirm Deletion
+
+
+

+ Are you sure you want to delete the data source + "{{ data_source.name }}"? +

+

+ This action cannot be undone. The data source and all associated chat + sessions ({{ data_source.chat_sessions.count }} sessions) will be + permanently deleted. +

-
- {% csrf_token %} -
- Cancel - -
-
-
-
-
-
+
+ {% csrf_token %} +
+ Cancel + +
+
+
+
+
+
{% endblock %} diff --git a/dashboard_project/templates/dashboard/data_source_detail.html b/dashboard_project/templates/dashboard/data_source_detail.html index 85ea42c..3fa0061 100644 --- a/dashboard_project/templates/dashboard/data_source_detail.html +++ b/dashboard_project/templates/dashboard/data_source_detail.html @@ -5,224 +5,251 @@ {% block title %}{{ data_source.name }} | Chat Analytics{% endblock %} {% block content %} - + -
-
-
-
-
Data Source Details
-
-
-
-
-

Name: {{ data_source.name }}

-

Uploaded At: {{ data_source.uploaded_at|date:"F d, Y H:i" }}

-

File: {{ data_source.file.name|split:"/"|last }}

-
-
-

Company: {{ data_source.company.name }}

-

Total Sessions: {{ page_obj.paginator.count }}

-

Description: {{ data_source.description }}

-
-
-
-
-
-
-
-
-
Filter Sessions
-
-
-
-
- - - -
-
-
-
-
-
+
+
+
+
+
Data Source Details
+
+
+
+
+

Name: {{ data_source.name }}

+

+ Uploaded At: + {{ data_source.uploaded_at|date:"F d, Y H:i" }} +

+

File: {{ data_source.file.name|split:"/"|last }}

+
+
+

Company: {{ data_source.company.name }}

+

Total Sessions: {{ page_obj.paginator.count }}

+

Description: {{ data_source.description }}

+
+
+
+
+
+
+
+
+
Filter Sessions
+
+
+
+
+ + + +
+
+
+
+
+
-
-
-
-
-
Chat Sessions ({{ page_obj.paginator.count }})
-
-
-
- - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
Session IDStart TimeCountryLanguageSentimentMessagesTokensCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country }}{{ session.language }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.messages_sent }}{{ session.tokens }}{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
No chat sessions found.
-
+
+
+
+
+
Chat Sessions ({{ page_obj.paginator.count }})
+
+
+
+ + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
Session IDStart TimeCountryLanguageSentimentMessagesTokensCategoryActions
{{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country }}{{ session.language }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.messages_sent }}{{ session.tokens }}{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
+ No chat sessions found. +
+
- {% if page_obj.paginator.num_pages > 1 %} -
-
-
-
+ {% if page_obj.has_next %} +
  • + + + +
  • +
  • + + + +
  • + {% else %} +
  • + + + +
  • +
  • + + + +
  • + {% endif %} + + + {% endif %} +
    +
    +
    +
    {% endblock %} diff --git a/dashboard_project/templates/dashboard/data_view.html b/dashboard_project/templates/dashboard/data_view.html index 4896317..1587cfd 100644 --- a/dashboard_project/templates/dashboard/data_view.html +++ b/dashboard_project/templates/dashboard/data_view.html @@ -5,270 +5,316 @@ {% block title %}Data View | Chat Analytics{% endblock %} {% block content %} -
    -

    Data View

    -
    -
    - - Back to Dashboard - - {% if selected_data_source %} - - View Source - - {% endif %} -
    - -
    -
    +
    +

    Data View

    +
    +
    + + Back to Dashboard + + {% if selected_data_source %} + + View Source + + {% endif %} +
    + +
    +
    - -
    -
    -
    -
    -
    Data Source Selection
    -
    -
    -
    -
    - -
    -
    - -
    -
    - -
    -
    -
    -
    -
    -
    + +
    +
    +
    +
    +
    Data Source Selection
    +
    +
    +
    +
    + +
    +
    + +
    +
    + +
    +
    +
    +
    +
    +
    - -
    -
    -
    -
    -
    Export Data
    -
    -
    -
    - - - + +
    +
    +
    +
    +
    Export Data
    +
    +
    + + + + -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - -
    - -
    -
    -
    -
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + +
    + +
    +
    +
    +
    - -
    -
    -
    -
    -
    - Chat Sessions - {% if selected_data_source %} - for {{ selected_data_source.name }} - {% endif %} - {% if view != 'all' %} - ({{ view|title }}) - {% endif %} -
    - {{ page_obj.paginator.count }} sessions -
    -
    - -
    -
    - Loading... -
    -

    Loading data...

    -
    + +
    +
    +
    +
    +
    + Chat Sessions + {% if selected_data_source %} + for {{ selected_data_source.name }} + {% endif %} + {% if view != 'all' %} + ({{ view|title }}) + {% endif %} +
    + {{ page_obj.paginator.count }} sessions +
    +
    + +
    +
    + Loading... +
    +

    Loading data...

    +
    - -
    {% include "dashboard/partials/data_table.html" %}
    -
    -
    -
    -
    + +
    + {% include "dashboard/partials/data_table.html" %} +
    +
    +
    +
    +
    - - {% if page_obj %} -
    -
    -
    -
    -
    Summary
    -
    -
    -
    -
    -
    -
    -
    Total Sessions
    -

    {{ page_obj.paginator.count }}

    -

    Chat conversations

    -
    -
    -
    -
    -
    -
    -
    Avg Response Time
    -

    {{ avg_response_time|floatformat:2 }}s

    -

    Average response

    -
    -
    -
    -
    -
    -
    -
    Avg Messages
    -

    {{ avg_messages|floatformat:1 }}

    -

    Per conversation

    -
    -
    -
    -
    -
    -
    -
    Escalation Rate
    -

    {{ escalation_rate|floatformat:1 }}%

    -

    Escalated sessions

    -
    -
    -
    -
    -
    -
    -
    -
    - {% endif %} + + {% if page_obj %} +
    +
    +
    +
    +
    Summary
    +
    +
    +
    +
    +
    +
    +
    Total Sessions
    +

    {{ page_obj.paginator.count }}

    +

    Chat conversations

    +
    +
    +
    +
    +
    +
    +
    Avg Response Time
    +

    {{ avg_response_time|floatformat:2 }}s

    +

    Average response

    +
    +
    +
    +
    +
    +
    +
    Avg Messages
    +

    {{ avg_messages|floatformat:1 }}

    +

    Per conversation

    +
    +
    +
    +
    +
    +
    +
    Escalation Rate
    +

    {{ escalation_rate|floatformat:1 }}%

    +

    Escalated sessions

    +
    +
    +
    +
    +
    +
    +
    +
    + {% endif %} {% endblock %} {% block extra_js %} - + {% endblock %} diff --git a/dashboard_project/templates/dashboard/no_company.html b/dashboard_project/templates/dashboard/no_company.html index 4b253f1..edfbfd5 100644 --- a/dashboard_project/templates/dashboard/no_company.html +++ b/dashboard_project/templates/dashboard/no_company.html @@ -4,37 +4,41 @@ {% block title %}No Company | Chat Analytics{% endblock %} {% block content %} -
    -

    No Company Association

    -
    +
    +

    No Company Association

    +
    -
    -
    -
    -
    -
    Account Not Associated with a Company
    -
    -
    -
    - -

    You are not currently associated with any company

    -

    You need to be associated with a company to access the dashboard.

    -
    +
    +
    +
    +
    +
    Account Not Associated with a Company
    +
    +
    +
    + +

    You are not currently associated with any company

    +

    + You need to be associated with a company to access the dashboard. +

    +
    -

    - Please contact an administrator to have your account assigned to a company. Once your - account is associated with a company, you'll be able to access the dashboard and its - features. -

    +

    + Please contact an administrator to have your account assigned to a company. + Once your account is associated with a company, you'll be able to access the + dashboard and its features. +

    - -
    -
    -
    -
    + +
    +
    +
    +
    {% endblock %} diff --git a/dashboard_project/templates/dashboard/partials/data_table.html b/dashboard_project/templates/dashboard/partials/data_table.html index 891c2de..19cfbb8 100644 --- a/dashboard_project/templates/dashboard/partials/data_table.html +++ b/dashboard_project/templates/dashboard/partials/data_table.html @@ -1,160 +1,160 @@
    - - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
    Session IDStart TimeCountryLanguageMessagesSentimentResponse TimeCategoryActions
    {{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country|default:"N/A" }}{{ session.language|default:"N/A" }}{{ session.messages_sent }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.avg_response_time|floatformat:2 }}s{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
    No chat sessions found.
    + + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    Session IDStart TimeCountryLanguageMessagesSentimentResponse TimeCategoryActions
    {{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }}{{ session.country|default:"N/A" }}{{ session.language|default:"N/A" }}{{ session.messages_sent }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.avg_response_time|floatformat:2 }}s{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
    No chat sessions found.
    {% if page_obj.paginator.num_pages > 1 %} - {% endif %} diff --git a/dashboard_project/templates/dashboard/partials/search_results_table.html b/dashboard_project/templates/dashboard/partials/search_results_table.html index 33aaed0..8df2601 100644 --- a/dashboard_project/templates/dashboard/partials/search_results_table.html +++ b/dashboard_project/templates/dashboard/partials/search_results_table.html @@ -1,164 +1,168 @@
    - - - - - - - - - - - - - - - - {% for session in page_obj %} - - - - - - - - - - - - {% empty %} - - - - {% endfor %} - -
    Session IDStart TimeData SourceCountryLanguageSentimentMessagesCategoryActions
    {{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }} - {{ session.data_source.name|truncatechars:15 }} - {{ session.country }}{{ session.language }} - {% if session.sentiment %} - {% if 'positive' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'negative' in session.sentiment|lower %} - {{ session.sentiment }} - {% elif 'neutral' in session.sentiment|lower %} - {{ session.sentiment }} - {% else %} - {{ session.sentiment }} - {% endif %} - {% else %} - N/A - {% endif %} - {{ session.messages_sent }}{{ session.category|default:"N/A" }} - {% if session.session_id %} - - - - {% else %} - - {% endif %} -
    No chat sessions found matching your criteria.
    + + + + + + + + + + + + + + + + {% for session in page_obj %} + + + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
    Session IDStart TimeData SourceCountryLanguageSentimentMessagesCategoryActions
    {{ session.session_id|truncatechars:10 }}{{ session.start_time|date:"M d, Y H:i" }} + {{ session.data_source.name|truncatechars:15 }} + {{ session.country }}{{ session.language }} + {% if session.sentiment %} + {% if 'positive' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'negative' in session.sentiment|lower %} + {{ session.sentiment }} + {% elif 'neutral' in session.sentiment|lower %} + {{ session.sentiment }} + {% else %} + {{ session.sentiment }} + {% endif %} + {% else %} + N/A + {% endif %} + {{ session.messages_sent }}{{ session.category|default:"N/A" }} + {% if session.session_id %} + + + + {% else %} + + {% endif %} +
    + No chat sessions found matching your criteria. +
    {% if page_obj.paginator.num_pages > 1 %} - {% endif %} diff --git a/dashboard_project/templates/dashboard/search_results.html b/dashboard_project/templates/dashboard/search_results.html index aae5303..37b3811 100644 --- a/dashboard_project/templates/dashboard/search_results.html +++ b/dashboard_project/templates/dashboard/search_results.html @@ -4,82 +4,90 @@ {% block title %}Search Results | Chat Analytics{% endblock %} {% block content %} -
    -

    Search Results

    - -
    +
    +

    Search Results

    + +
    -
    -
    -
    -
    -
    Search Chat Sessions
    -
    -
    -
    -
    - - {% if data_source %} - - {% endif %} - -
    -
    - Search by session ID, country, language, sentiment, category, or message - content. -
    -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    Search Chat Sessions
    +
    +
    +
    +
    + + {% if data_source %} + + {% endif %} + +
    +
    + Search by session ID, country, language, sentiment, category, or + message content. +
    +
    +
    +
    +
    +
    -
    -
    -
    -
    -
    - Results {% if query %}for "{{ query }}"{% endif %} - {% if data_source %}in {{ data_source.name }}{% endif %} - ({{ page_obj.paginator.count }}) -
    -
    -
    - -
    -
    - Loading... -
    -

    Loading data...

    -
    +
    +
    +
    +
    +
    + Results {% if query %}for "{{ query }}"{% endif %} + {% if data_source %}in {{ data_source.name }}{% endif %} + ({{ page_obj.paginator.count }}) +
    +
    +
    + +
    +
    + Loading... +
    +

    Loading data...

    +
    - -
    - {% include "dashboard/partials/search_results_table.html" %} -
    -
    -
    -
    -
    + +
    + {% include "dashboard/partials/search_results_table.html" %} +
    +
    +
    +
    +
    {% endblock %} {% block extra_js %} - + {% endblock %} diff --git a/dashboard_project/templates/dashboard/upload.html b/dashboard_project/templates/dashboard/upload.html index 3599075..d87ff89 100644 --- a/dashboard_project/templates/dashboard/upload.html +++ b/dashboard_project/templates/dashboard/upload.html @@ -6,192 +6,192 @@ {% block title %}Upload Data | Chat Analytics{% endblock %} {% block content %} -
    -

    Upload Data

    - -
    +
    +

    Upload Data

    + +
    -
    -
    -
    -
    -
    Upload CSV File
    -
    -
    -
    - {% csrf_token %} {{ form|crispy }} -
    - -
    -
    -
    -
    -
    +
    +
    +
    +
    +
    Upload CSV File
    +
    +
    +
    + {% csrf_token %} {{ form|crispy }} +
    + +
    +
    +
    +
    +
    -
    -
    -
    -
    CSV File Format
    -
    -
    -

    The CSV file should contain the following columns:

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    ColumnDescriptionType
    session_idUnique identifier for the chat sessionString
    start_timeWhen the session startedDatetime
    end_timeWhen the session endedDatetime
    ip_addressIP address of the userString
    countryCountry of the userString
    languageLanguage used in the conversationString
    messages_sentNumber of messages in the conversationInteger
    sentimentSentiment analysis of the conversationString
    escalatedWhether the conversation was escalatedBoolean
    forwarded_hrWhether the conversation was forwarded to HRBoolean
    full_transcriptFull transcript of the conversationText
    avg_response_timeAverage response time in secondsFloat
    tokensTotal number of tokens usedInteger
    tokens_eurCost of tokens in EURFloat
    categoryCategory of the conversationString
    initial_msgFirst message from the userText
    user_ratingUser rating of the conversationString
    -
    -
    -
    -
    +
    +
    +
    +
    CSV File Format
    +
    +
    +

    The CSV file should contain the following columns:

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ColumnDescriptionType
    session_idUnique identifier for the chat sessionString
    start_timeWhen the session startedDatetime
    end_timeWhen the session endedDatetime
    ip_addressIP address of the userString
    countryCountry of the userString
    languageLanguage used in the conversationString
    messages_sentNumber of messages in the conversationInteger
    sentimentSentiment analysis of the conversationString
    escalatedWhether the conversation was escalatedBoolean
    forwarded_hrWhether the conversation was forwarded to HRBoolean
    full_transcriptFull transcript of the conversationText
    avg_response_timeAverage response time in secondsFloat
    tokensTotal number of tokens usedInteger
    tokens_eurCost of tokens in EURFloat
    categoryCategory of the conversationString
    initial_msgFirst message from the userText
    user_ratingUser rating of the conversationString
    +
    +
    +
    +
    - {% if data_sources %} -
    -
    -
    -
    -
    Uploaded Data Sources
    -
    -
    -
    - - - - - - - - - - - - - {% for data_source in data_sources %} - - - - - - - - - {% endfor %} - -
    NameDescriptionUploadedFileSessionsActions
    {{ data_source.name }}{{ data_source.description|truncatechars:50 }}{{ data_source.uploaded_at|date:"M d, Y H:i" }}{{ data_source.file.name|split:"/"|last }}{{ data_source.chat_sessions.count }} - - - - - - -
    -
    -
    -
    -
    -
    - {% endif %} + {% if data_sources %} +
    +
    +
    +
    +
    Uploaded Data Sources
    +
    +
    +
    + + + + + + + + + + + + + {% for data_source in data_sources %} + + + + + + + + + {% endfor %} + +
    NameDescriptionUploadedFileSessionsActions
    {{ data_source.name }}{{ data_source.description|truncatechars:50 }}{{ data_source.uploaded_at|date:"M d, Y H:i" }}{{ data_source.file.name|split:"/"|last }}{{ data_source.chat_sessions.count }} + + + + + + +
    +
    +
    +
    +
    +
    + {% endif %} {% endblock %} diff --git a/docker-compose.yml b/docker-compose.yml index 133a5ba..068fe7d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: - DEBUG=0 - SECRET_KEY=your_secret_key_here - ALLOWED_HOSTS=localhost,127.0.0.1 + - DJANGO_SETTINGS_MODULE=dashboard_project.settings depends_on: - db @@ -27,6 +28,8 @@ services: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=dashboard_db + ports: + - "5432:5432" nginx: image: nginx:latest diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index bcd5b4d..0000000 --- a/package-lock.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "LiveGraphsDjango", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "prettier": "^3.5.3", - "prettier-plugin-jinja-template": "^2.1.0" - } - }, - "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-jinja-template": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/prettier-plugin-jinja-template/-/prettier-plugin-jinja-template-2.1.0.tgz", - "integrity": "sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "prettier": "^3.0.0" - } - } - } -} diff --git a/package.json b/package.json index 07187a0..0c42507 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,17 @@ { - "devDependencies": { - "prettier": "^3.5.3", - "prettier-plugin-jinja-template": "^2.1.0" - } + "name": "livegraphsdjango", + "version": "0.1.0", + "description": "Live Graphs Django Dashboard", + "private": true, + "scripts": { + "lint": "eslint dashboard_project/static/js/", + "format": "prettier --write \"dashboard_project/static/**/*.{js,css,html}\"", + "format:check": "prettier --check \"dashboard_project/static/**/*.{js,css,html}\"", + "stylelint": "stylelint \"dashboard_project/static/css/**/*.css\" --fix", + "stylelint:check": "stylelint \"dashboard_project/static/css/**/*.css\"" + }, + "devDependencies": { + "prettier": "^3.5.3", + "prettier-plugin-jinja-template": "^2.1.0" + } } diff --git a/pyproject.toml b/pyproject.toml index 35129db..09eacda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,9 +1,21 @@ [project] name = "livegraphsdjango" version = "0.1.0" -description = "Add your description here" +description = "Live Graphs Django Dashboard" readme = "README.md" requires-python = ">=3.13" +authors = [{ name = "LiveGraphs Team" }] +license = { text = "MIT" } + +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.13", + "Framework :: Django", + "Framework :: Django :: 5.2", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] + dependencies = [ "crispy-bootstrap5>=2025.4", "django>=5.2.1", @@ -17,16 +29,29 @@ dependencies = [ "whitenoise>=6.9.0", ] -[project.scripts] -# Django management commands -"manage" = "dashboard_project.manage:main" -"runserver" = "dashboard_project.manage:main" -"migrate" = "dashboard_project.manage:main" -"makemigrations" = "dashboard_project.manage:main" -"collectstatic" = "dashboard_project.manage:main" -"createsuperuser" = "dashboard_project.manage:main" -"shell" = "dashboard_project.manage:main" -"test" = "dashboard_project.manage:main" +[dependency-groups] +dev = [ + "bandit>=1.8.3", + "black>=25.1.0", + "coverage>=7.8.0", + "django-debug-toolbar>=5.2.0", + "django-stubs>=5.2.0", + "mypy>=1.15.0", + "pre-commit>=4.2.0", + "pytest>=8.3.5", + "pytest-django>=4.11.1", + "ruff>=0.11.10", +] + +[build-system] +requires = ["setuptools>=69.0.0", "wheel>=0.42.0"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +packages = ["dashboard_project"] + +[tool.setuptools.package-data] +"dashboard_project" = ["static/**/*", "templates/**/*", "media/**/*"] [tool.ruff] # Exclude a variety of commonly ignored directories. @@ -67,7 +92,7 @@ indent-width = 4 target-version = "py313" [tool.ruff.lint] -select = ["E", "F", "I"] +select = ["E", "F", "I", "B", "C4", "ARG", "SIM", "PERF"] ignore = ["E501"] fixable = ["ALL"] unfixable = [] @@ -76,3 +101,55 @@ unfixable = [] quote-style = "double" indent-style = "space" line-ending = "lf" + +[tool.bandit] +exclude_dirs = ["tests", "venv", ".venv", ".git", "__pycache__", "migrations", "**/create_sample_data.py"] +skips = ["B101"] +targets = ["dashboard_project"] + +[tool.mypy] +python_version = "3.13" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false +disallow_incomplete_defs = false +plugins = ["mypy_django_plugin.main"] + +[[tool.mypy.overrides]] +module = ["django.*", "rest_framework.*"] +ignore_missing_imports = true + +[tool.django-stubs] +django_settings_module = "dashboard_project.settings" + +[tool.pytest.ini_options] +DJANGO_SETTINGS_MODULE = "dashboard_project.settings" +python_files = "test_*.py" +testpaths = ["dashboard_project"] +filterwarnings = [ + "ignore::DeprecationWarning", + "ignore::PendingDeprecationWarning", +] + +[tool.coverage.run] +source = ["dashboard_project"] +omit = [ + "dashboard_project/manage.py", + "dashboard_project/*/migrations/*", + "dashboard_project/*/tests/*", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise NotImplementedError", + "if __name__ == .__main__.:", + "pass", + "raise ImportError", +] + +[project.urls] +"Documentation" = "https://github.com/kjanat/livegraphsdjango#readme" +"Source" = "https://github.com/kjanat/livegraphsdjango" +"Bug Tracker" = "https://github.com/kjanat/livegraphsdjango/issues" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b8c9e89 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,320 @@ +# This file was autogenerated by uv via the following command: +# uv export --frozen --output-file=requirements.txt +-e . +asgiref==3.8.1 \ + --hash=sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47 \ + --hash=sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590 + # via + # django + # django-allauth + # django-stubs +bandit==1.8.3 \ + --hash=sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8 \ + --hash=sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a +black==25.1.0 \ + --hash=sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171 \ + --hash=sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666 \ + --hash=sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f \ + --hash=sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717 \ + --hash=sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18 \ + --hash=sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3 +cfgv==3.4.0 \ + --hash=sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9 \ + --hash=sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560 + # via pre-commit +click==8.2.0 \ + --hash=sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c \ + --hash=sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d + # via black +colorama==0.4.6 ; sys_platform == 'win32' \ + --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ + --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 + # via + # bandit + # click + # pytest +coverage==7.8.0 \ + --hash=sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3 \ + --hash=sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25 \ + --hash=sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257 \ + --hash=sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada \ + --hash=sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64 \ + --hash=sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067 \ + --hash=sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733 \ + --hash=sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008 \ + --hash=sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd \ + --hash=sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00 \ + --hash=sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501 \ + --hash=sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42 \ + --hash=sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487 \ + --hash=sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4 \ + --hash=sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73 \ + --hash=sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a \ + --hash=sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1 \ + --hash=sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7 \ + --hash=sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d \ + --hash=sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502 \ + --hash=sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323 \ + --hash=sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883 +crispy-bootstrap5==2025.4 \ + --hash=sha256:51efa19c7d40e339774a6fe23407e83b95b7634cad6de70fd1f1093131bea1d9 \ + --hash=sha256:d675ea7e245048905077dfe16bf1fa1ee16842f52fe88164ccc8a5e2d11119b3 + # via livegraphsdjango +distlib==0.3.9 \ + --hash=sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87 \ + --hash=sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403 + # via virtualenv +django==5.2.1 \ + --hash=sha256:57fe1f1b59462caed092c80b3dd324fd92161b620d59a9ba9181c34746c97284 \ + --hash=sha256:a9b680e84f9a0e71da83e399f1e922e1ab37b2173ced046b541c72e1589a5961 + # via + # crispy-bootstrap5 + # django-allauth + # django-crispy-forms + # django-debug-toolbar + # django-stubs + # django-stubs-ext + # livegraphsdjango +django-allauth==65.8.0 \ + --hash=sha256:9da589d99d412740629333a01865a90c95c97e0fae0cde789aa45a8fda90e83b + # via livegraphsdjango +django-crispy-forms==2.4 \ + --hash=sha256:5a4b99876cfb1bdd3e47727731b6d4197c51c0da502befbfbec6a93010b02030 \ + --hash=sha256:915e1ffdeb2987d78b33fabfeff8e5203c8776aa910a3a659a2c514ca125f3bd + # via + # crispy-bootstrap5 + # livegraphsdjango +django-debug-toolbar==5.2.0 \ + --hash=sha256:15627f4c2836a9099d795e271e38e8cf5204ccd79d5dbcd748f8a6c284dcd195 \ + --hash=sha256:9e7f0145e1a1b7d78fcc3b53798686170a5b472d9cf085d88121ff823e900821 +django-stubs==5.2.0 \ + --hash=sha256:07e25c2d3cbff5be540227ff37719cc89f215dfaaaa5eb038a75b01bbfbb2722 \ + --hash=sha256:cd52da033489afc1357d6245f49e3cc57bf49015877253fb8efc6722ea3d2d2b +django-stubs-ext==5.2.0 \ + --hash=sha256:00c4ae307b538f5643af761a914c3f8e4e3f25f4e7c6d7098f1906c0d8f2aac9 \ + --hash=sha256:b27ae0aab970af4894ba4e9b3fcd3e03421dc8731516669659ee56122d148b23 + # via django-stubs +filelock==3.18.0 \ + --hash=sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2 \ + --hash=sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de + # via virtualenv +gunicorn==23.0.0 \ + --hash=sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d \ + --hash=sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec + # via livegraphsdjango +identify==2.6.10 \ + --hash=sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8 \ + --hash=sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25 + # via pre-commit +iniconfig==2.1.0 \ + --hash=sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7 \ + --hash=sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760 + # via pytest +markdown-it-py==3.0.0 \ + --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ + --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb + # via rich +mdurl==0.1.2 \ + --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ + --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba + # via markdown-it-py +mypy==1.15.0 \ + --hash=sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43 \ + --hash=sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e \ + --hash=sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d \ + --hash=sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445 \ + --hash=sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5 \ + --hash=sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf \ + --hash=sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357 \ + --hash=sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036 +mypy-extensions==1.1.0 \ + --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ + --hash=sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558 + # via + # black + # mypy +narwhals==1.39.1 \ + --hash=sha256:68d0f29c760f1a9419ada537f35f21ff202b0be1419e6d22135a0352c6d96deb \ + --hash=sha256:cf15389e6f8c5321e8cd0ca8b5bace3b1aea5f5622fa59dfd64821998741d836 + # via plotly +nodeenv==1.9.1 \ + --hash=sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f \ + --hash=sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9 + # via pre-commit +numpy==2.2.5 \ + --hash=sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a \ + --hash=sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4 \ + --hash=sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e \ + --hash=sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9 \ + --hash=sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d \ + --hash=sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376 \ + --hash=sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372 \ + --hash=sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191 \ + --hash=sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f \ + --hash=sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73 \ + --hash=sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0 \ + --hash=sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19 \ + --hash=sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba \ + --hash=sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133 \ + --hash=sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7 \ + --hash=sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291 \ + --hash=sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066 \ + --hash=sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b \ + --hash=sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8 \ + --hash=sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471 \ + --hash=sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6 + # via + # livegraphsdjango + # pandas +packaging==25.0 \ + --hash=sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484 \ + --hash=sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f + # via + # black + # gunicorn + # plotly + # pytest +pandas==2.2.3 \ + --hash=sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d \ + --hash=sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4 \ + --hash=sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0 \ + --hash=sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28 \ + --hash=sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18 \ + --hash=sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468 \ + --hash=sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667 \ + --hash=sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d \ + --hash=sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb \ + --hash=sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659 \ + --hash=sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a \ + --hash=sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2 \ + --hash=sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015 \ + --hash=sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24 + # via livegraphsdjango +pathspec==0.12.1 \ + --hash=sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08 \ + --hash=sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712 + # via black +pbr==6.1.1 \ + --hash=sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76 \ + --hash=sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b + # via stevedore +platformdirs==4.3.8 \ + --hash=sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc \ + --hash=sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4 + # via + # black + # virtualenv +plotly==6.1.0 \ + --hash=sha256:a29d3ed523c9d7960095693af1ee52689830df0f9c6bae3e5e92c20c4f5684c3 \ + --hash=sha256:f13f497ccc2d97f06f771a30b27fab0cbd220f2975865f4ecbc75057135521de + # via livegraphsdjango +pluggy==1.6.0 \ + --hash=sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3 \ + --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 + # via pytest +pre-commit==4.2.0 \ + --hash=sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146 \ + --hash=sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd +pygments==2.19.1 \ + --hash=sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f \ + --hash=sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c + # via rich +pytest==8.3.5 \ + --hash=sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820 \ + --hash=sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845 + # via pytest-django +pytest-django==4.11.1 \ + --hash=sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10 \ + --hash=sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991 +python-dateutil==2.9.0.post0 \ + --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ + --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + # via pandas +python-dotenv==1.1.0 \ + --hash=sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5 \ + --hash=sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d + # via livegraphsdjango +pytz==2025.2 \ + --hash=sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3 \ + --hash=sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00 + # via pandas +pyyaml==6.0.2 \ + --hash=sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133 \ + --hash=sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484 \ + --hash=sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc \ + --hash=sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1 \ + --hash=sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652 \ + --hash=sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5 \ + --hash=sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563 \ + --hash=sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183 \ + --hash=sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e \ + --hash=sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba + # via + # bandit + # pre-commit +rich==14.0.0 \ + --hash=sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0 \ + --hash=sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725 + # via bandit +ruff==0.11.10 \ + --hash=sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca \ + --hash=sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f \ + --hash=sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad \ + --hash=sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b \ + --hash=sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125 \ + --hash=sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641 \ + --hash=sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224 \ + --hash=sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947 \ + --hash=sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4 \ + --hash=sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58 \ + --hash=sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5 \ + --hash=sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed \ + --hash=sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6 \ + --hash=sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2 \ + --hash=sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19 \ + --hash=sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523 \ + --hash=sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1 \ + --hash=sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2 +setuptools==80.7.1 \ + --hash=sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009 \ + --hash=sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552 + # via pbr +six==1.17.0 \ + --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ + --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 + # via python-dateutil +sqlparse==0.5.3 \ + --hash=sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272 \ + --hash=sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca + # via + # django + # django-debug-toolbar +stevedore==5.4.1 \ + --hash=sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b \ + --hash=sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe + # via bandit +types-pyyaml==6.0.12.20250516 \ + --hash=sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530 \ + --hash=sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba + # via django-stubs +typing-extensions==4.13.2 \ + --hash=sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c \ + --hash=sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef + # via + # django-stubs + # django-stubs-ext + # mypy +tzdata==2025.2 \ + --hash=sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8 \ + --hash=sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9 + # via + # django + # pandas +virtualenv==20.31.2 \ + --hash=sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11 \ + --hash=sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af + # via pre-commit +whitenoise==6.9.0 \ + --hash=sha256:8c4a7c9d384694990c26f3047e118c691557481d624f069b7f7752a2f735d609 \ + --hash=sha256:c8a489049b7ee9889617bb4c274a153f3d979e8f51d2efd0f5b403caf41c57df + # via livegraphsdjango diff --git a/setup.py b/setup.py deleted file mode 100644 index d4281f3..0000000 --- a/setup.py +++ /dev/null @@ -1,19 +0,0 @@ -from setuptools import setup - -setup( - name="livegraphsdjango", - version="0.1.0", - packages=["dashboard_project"], - entry_points={ - "console_scripts": [ - "manage=dashboard_project.manage:main", - "runserver=dashboard_project.__main__:main", - "migrate=dashboard_project.__main__:main", - "makemigrations=dashboard_project.__main__:main", - "collectstatic=dashboard_project.__main__:main", - "createsuperuser=dashboard_project.__main__:main", - "shell=dashboard_project.__main__:main", - "test=dashboard_project.__main__:main", - ], - }, -) diff --git a/uv.lock b/uv.lock index 6510154..7701185 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,100 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/e3/893e8757be2612e6c266d9bb58ad2e3651524b5b40cf56761e985a28b13e/asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47", size = 23828, upload-time = "2024-03-22T14:39:34.521Z" }, ] +[[package]] +name = "bandit" +version = "1.8.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "pyyaml" }, + { name = "rich" }, + { name = "stevedore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/a5/144a45f8e67df9d66c3bc3f7e69a39537db8bff1189ab7cff4e9459215da/bandit-1.8.3.tar.gz", hash = "sha256:f5847beb654d309422985c36644649924e0ea4425c76dec2e89110b87506193a", size = 4232005, upload-time = "2025-02-17T05:24:57.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/85/db74b9233e0aa27ec96891045c5e920a64dd5cbccd50f8e64e9460f48d35/bandit-1.8.3-py3-none-any.whl", hash = "sha256:28f04dc0d258e1dd0f99dee8eefa13d1cb5e3fde1a5ab0c523971f97b289bcd8", size = 129078, upload-time = "2025-02-17T05:24:54.068Z" }, +] + +[[package]] +name = "black" +version = "25.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "mypy-extensions" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449, upload-time = "2025-01-29T04:15:40.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673, upload-time = "2025-01-29T05:37:20.574Z" }, + { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190, upload-time = "2025-01-29T05:37:22.106Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926, upload-time = "2025-01-29T04:18:58.564Z" }, + { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613, upload-time = "2025-01-29T04:19:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646, upload-time = "2025-01-29T04:15:38.082Z" }, +] + +[[package]] +name = "cfgv" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" }, +] + +[[package]] +name = "click" +version = "8.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/4f/2251e65033ed2ce1e68f00f91a0294e0f80c80ae8c3ebbe2f12828c4cd53/coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501", size = 811872, upload-time = "2025-03-30T20:36:45.376Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/21/87e9b97b568e223f3438d93072479c2f36cc9b3f6b9f7094b9d50232acc0/coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd", size = 211708, upload-time = "2025-03-30T20:35:47.417Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/882d08b28a0d19c9c4c2e8a1c6ebe1f79c9c839eb46d4fca3bd3b34562b9/coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00", size = 211981, upload-time = "2025-03-30T20:35:49.002Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1d/ce99612ebd58082fbe3f8c66f6d8d5694976c76a0d474503fa70633ec77f/coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64", size = 245495, upload-time = "2025-03-30T20:35:51.073Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/6115abe97df98db6b2bd76aae395fcc941d039a7acd25f741312ced9a78f/coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067", size = 242538, upload-time = "2025-03-30T20:35:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/cb/74/2f8cc196643b15bc096d60e073691dadb3dca48418f08bc78dd6e899383e/coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008", size = 244561, upload-time = "2025-03-30T20:35:54.658Z" }, + { url = "https://files.pythonhosted.org/packages/22/70/c10c77cd77970ac965734fe3419f2c98665f6e982744a9bfb0e749d298f4/coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733", size = 244633, upload-time = "2025-03-30T20:35:56.221Z" }, + { url = "https://files.pythonhosted.org/packages/38/5a/4f7569d946a07c952688debee18c2bb9ab24f88027e3d71fd25dbc2f9dca/coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323", size = 242712, upload-time = "2025-03-30T20:35:57.801Z" }, + { url = "https://files.pythonhosted.org/packages/bb/a1/03a43b33f50475a632a91ea8c127f7e35e53786dbe6781c25f19fd5a65f8/coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3", size = 244000, upload-time = "2025-03-30T20:35:59.378Z" }, + { url = "https://files.pythonhosted.org/packages/6a/89/ab6c43b1788a3128e4d1b7b54214548dcad75a621f9d277b14d16a80d8a1/coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d", size = 214195, upload-time = "2025-03-30T20:36:01.005Z" }, + { url = "https://files.pythonhosted.org/packages/12/12/6bf5f9a8b063d116bac536a7fb594fc35cb04981654cccb4bbfea5dcdfa0/coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487", size = 214998, upload-time = "2025-03-30T20:36:03.006Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e6/1e9df74ef7a1c983a9c7443dac8aac37a46f1939ae3499424622e72a6f78/coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25", size = 212541, upload-time = "2025-03-30T20:36:04.638Z" }, + { url = "https://files.pythonhosted.org/packages/04/51/c32174edb7ee49744e2e81c4b1414ac9df3dacfcb5b5f273b7f285ad43f6/coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42", size = 212767, upload-time = "2025-03-30T20:36:06.503Z" }, + { url = "https://files.pythonhosted.org/packages/e9/8f/f454cbdb5212f13f29d4a7983db69169f1937e869a5142bce983ded52162/coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502", size = 256997, upload-time = "2025-03-30T20:36:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e6/74/2bf9e78b321216d6ee90a81e5c22f912fc428442c830c4077b4a071db66f/coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1", size = 252708, upload-time = "2025-03-30T20:36:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/92/4d/50d7eb1e9a6062bee6e2f92e78b0998848a972e9afad349b6cdde6fa9e32/coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4", size = 255046, upload-time = "2025-03-30T20:36:11.409Z" }, + { url = "https://files.pythonhosted.org/packages/40/9e/71fb4e7402a07c4198ab44fc564d09d7d0ffca46a9fb7b0a7b929e7641bd/coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73", size = 256139, upload-time = "2025-03-30T20:36:13.86Z" }, + { url = "https://files.pythonhosted.org/packages/49/1a/78d37f7a42b5beff027e807c2843185961fdae7fe23aad5a4837c93f9d25/coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a", size = 254307, upload-time = "2025-03-30T20:36:16.074Z" }, + { url = "https://files.pythonhosted.org/packages/58/e9/8fb8e0ff6bef5e170ee19d59ca694f9001b2ec085dc99b4f65c128bb3f9a/coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883", size = 255116, upload-time = "2025-03-30T20:36:18.033Z" }, + { url = "https://files.pythonhosted.org/packages/56/b0/d968ecdbe6fe0a863de7169bbe9e8a476868959f3af24981f6a10d2b6924/coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada", size = 214909, upload-time = "2025-03-30T20:36:19.644Z" }, + { url = "https://files.pythonhosted.org/packages/87/e9/d6b7ef9fecf42dfb418d93544af47c940aa83056c49e6021a564aafbc91f/coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257", size = 216068, upload-time = "2025-03-30T20:36:21.282Z" }, + { url = "https://files.pythonhosted.org/packages/59/f1/4da7717f0063a222db253e7121bd6a56f6fb1ba439dcc36659088793347c/coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7", size = 203435, upload-time = "2025-03-30T20:36:43.61Z" }, +] + [[package]] name = "crispy-bootstrap5" version = "2025.4" @@ -24,6 +118,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/9a/4f1166cc82c9f777cf9a5bc2a75171d63301ac317c5de8f59bd44bfe2b7a/crispy_bootstrap5-2025.4-py3-none-any.whl", hash = "sha256:51efa19c7d40e339774a6fe23407e83b95b7634cad6de70fd1f1093131bea1d9", size = 24772, upload-time = "2025-04-02T12:33:14.904Z" }, ] +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, +] + [[package]] name = "django" version = "5.2.1" @@ -60,6 +163,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/ec/a25f81e56a674e63cf6c3dd8e36b1b3fecc238fecd6098504adc0cc61402/django_crispy_forms-2.4-py3-none-any.whl", hash = "sha256:5a4b99876cfb1bdd3e47727731b6d4197c51c0da502befbfbec6a93010b02030", size = 31446, upload-time = "2025-04-13T07:24:58.516Z" }, ] +[[package]] +name = "django-debug-toolbar" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "sqlparse" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/9f/97ba2648f66fa208fc7f19d6895586d08bc5f0ab930a1f41032e60f31a41/django_debug_toolbar-5.2.0.tar.gz", hash = "sha256:9e7f0145e1a1b7d78fcc3b53798686170a5b472d9cf085d88121ff823e900821", size = 297901, upload-time = "2025-04-29T05:23:57.533Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/c2/ed3cb815002664349e9e50799b8c00ef15941f4cad797247cadbdeebab02/django_debug_toolbar-5.2.0-py3-none-any.whl", hash = "sha256:15627f4c2836a9099d795e271e38e8cf5204ccd79d5dbcd748f8a6c284dcd195", size = 262834, upload-time = "2025-04-29T05:23:55.472Z" }, +] + +[[package]] +name = "django-stubs" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asgiref" }, + { name = "django" }, + { name = "django-stubs-ext" }, + { name = "types-pyyaml" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/79/7e7ad8b4bac545c8e608fa8db0bd061977f93035a112be78a7f3ffc6ff66/django_stubs-5.2.0.tar.gz", hash = "sha256:07e25c2d3cbff5be540227ff37719cc89f215dfaaaa5eb038a75b01bbfbb2722", size = 276297, upload-time = "2025-04-26T10:49:04.974Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/01/5913ba5514337f3896c7bcbff6075808184dd303cd0fc3ecc289ec7e0c96/django_stubs-5.2.0-py3-none-any.whl", hash = "sha256:cd52da033489afc1357d6245f49e3cc57bf49015877253fb8efc6722ea3d2d2b", size = 481836, upload-time = "2025-04-26T10:49:02.97Z" }, +] + +[[package]] +name = "django-stubs-ext" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/7a/84338605817960942c1ea9d852685923ccccd0d91ba0d49532605973491f/django_stubs_ext-5.2.0.tar.gz", hash = "sha256:00c4ae307b538f5643af761a914c3f8e4e3f25f4e7c6d7098f1906c0d8f2aac9", size = 9618, upload-time = "2025-04-26T10:48:38.05Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/65/9f5ca467d84a67c0c547f10b0ece9fd9c26c5efc818a01bf6a3d306c2a0c/django_stubs_ext-5.2.0-py3-none-any.whl", hash = "sha256:b27ae0aab970af4894ba4e9b3fcd3e03421dc8731516669659ee56122d148b23", size = 9066, upload-time = "2025-04-26T10:48:36.032Z" }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, +] + [[package]] name = "gunicorn" version = "23.0.0" @@ -72,10 +226,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" }, ] +[[package]] +name = "identify" +version = "2.6.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/83/b6ea0334e2e7327084a46aaaf71f2146fc061a192d6518c0d020120cd0aa/identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8", size = 99201, upload-time = "2025-04-19T15:10:38.32Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2b/d3/85feeba1d097b81a44bcffa6a0beab7b4dfffe78e82fc54978d3ac380736/identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25", size = 99101, upload-time = "2025-04-19T15:10:36.701Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + [[package]] name = "livegraphsdjango" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "crispy-bootstrap5" }, { name = "django" }, @@ -89,6 +261,20 @@ dependencies = [ { name = "whitenoise" }, ] +[package.dev-dependencies] +dev = [ + { name = "bandit" }, + { name = "black" }, + { name = "coverage" }, + { name = "django-debug-toolbar" }, + { name = "django-stubs" }, + { name = "mypy" }, + { name = "pre-commit" }, + { name = "pytest" }, + { name = "pytest-django" }, + { name = "ruff" }, +] + [package.metadata] requires-dist = [ { name = "crispy-bootstrap5", specifier = ">=2025.4" }, @@ -103,6 +289,69 @@ requires-dist = [ { name = "whitenoise", specifier = ">=6.9.0" }, ] +[package.metadata.requires-dev] +dev = [ + { name = "bandit", specifier = ">=1.8.3" }, + { name = "black", specifier = ">=25.1.0" }, + { name = "coverage", specifier = ">=7.8.0" }, + { name = "django-debug-toolbar", specifier = ">=5.2.0" }, + { name = "django-stubs", specifier = ">=5.2.0" }, + { name = "mypy", specifier = ">=1.15.0" }, + { name = "pre-commit", specifier = ">=4.2.0" }, + { name = "pytest", specifier = ">=8.3.5" }, + { name = "pytest-django", specifier = ">=4.11.1" }, + { name = "ruff", specifier = ">=0.11.10" }, +] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mypy" +version = "1.15.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ce/43/d5e49a86afa64bd3839ea0d5b9c7103487007d728e1293f52525d6d5486a/mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43", size = 3239717, upload-time = "2025-02-05T03:50:34.655Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/9b/fd2e05d6ffff24d912f150b87db9e364fa8282045c875654ce7e32fffa66/mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445", size = 10788592, upload-time = "2025-02-05T03:48:55.789Z" }, + { url = "https://files.pythonhosted.org/packages/74/37/b246d711c28a03ead1fd906bbc7106659aed7c089d55fe40dd58db812628/mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d", size = 9753611, upload-time = "2025-02-05T03:48:44.581Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ac/395808a92e10cfdac8003c3de9a2ab6dc7cde6c0d2a4df3df1b815ffd067/mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5", size = 11438443, upload-time = "2025-02-05T03:49:25.514Z" }, + { url = "https://files.pythonhosted.org/packages/d2/8b/801aa06445d2de3895f59e476f38f3f8d610ef5d6908245f07d002676cbf/mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036", size = 12402541, upload-time = "2025-02-05T03:49:57.623Z" }, + { url = "https://files.pythonhosted.org/packages/c7/67/5a4268782eb77344cc613a4cf23540928e41f018a9a1ec4c6882baf20ab8/mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357", size = 12494348, upload-time = "2025-02-05T03:48:52.361Z" }, + { url = "https://files.pythonhosted.org/packages/83/3e/57bb447f7bbbfaabf1712d96f9df142624a386d98fb026a761532526057e/mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf", size = 9373648, upload-time = "2025-02-05T03:49:11.395Z" }, + { url = "https://files.pythonhosted.org/packages/09/4e/a7d65c7322c510de2c409ff3828b03354a7c43f5a8ed458a7a131b41c7b9/mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e", size = 2221777, upload-time = "2025-02-05T03:50:08.348Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + [[package]] name = "narwhals" version = "1.39.1" @@ -112,6 +361,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/c4/b83520ecc27840a4d58bd585ae0ec0f8a4f2b0c5a965b66749254a54de0e/narwhals-1.39.1-py3-none-any.whl", hash = "sha256:68d0f29c760f1a9419ada537f35f21ff202b0be1419e6d22135a0352c6d96deb", size = 355009, upload-time = "2025-05-15T17:45:07.954Z" }, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, +] + [[package]] name = "numpy" version = "2.2.5" @@ -176,6 +434,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, ] +[[package]] +name = "pathspec" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, +] + +[[package]] +name = "pbr" +version = "6.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/d2/510cc0d218e753ba62a1bc1434651db3cd797a9716a0a66cc714cb4f0935/pbr-6.1.1.tar.gz", hash = "sha256:93ea72ce6989eb2eed99d0f75721474f69ad88128afdef5ac377eb797c4bf76b", size = 125702, upload-time = "2025-02-04T14:28:06.514Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/ac/684d71315abc7b1214d59304e23a982472967f6bf4bde5a98f1503f648dc/pbr-6.1.1-py2.py3-none-any.whl", hash = "sha256:38d4daea5d9fa63b3f626131b9d34947fd0c8be9b05a29276870580050a25a76", size = 108997, upload-time = "2025-02-04T14:28:03.168Z" }, +] + +[[package]] +name = "platformdirs" +version = "4.3.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" }, +] + [[package]] name = "plotly" version = "6.1.0" @@ -189,6 +477,67 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/11/83ae52318353f9da4a88cc23e7f9dbc3d449b3f0fd6158fba15eb3c3b816/plotly-6.1.0-py3-none-any.whl", hash = "sha256:a29d3ed523c9d7960095693af1ee52689830df0f9c6bae3e5e92c20c4f5684c3", size = 16118476, upload-time = "2025-05-15T16:04:30.81Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pre-commit" +version = "4.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest-django" +version = "4.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/fb/55d580352db26eb3d59ad50c64321ddfe228d3d8ac107db05387a2fadf3a/pytest_django-4.11.1.tar.gz", hash = "sha256:a949141a1ee103cb0e7a20f1451d355f83f5e4a5d07bdd4dcfdd1fd0ff227991", size = 86202, upload-time = "2025-04-03T18:56:09.338Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/ac/bd0608d229ec808e51a21044f3f2f27b9a37e7a0ebaca7247882e67876af/pytest_django-4.11.1-py3-none-any.whl", hash = "sha256:1b63773f648aa3d8541000c26929c1ea63934be1cfa674c76436966d73fe6a10", size = 25281, upload-time = "2025-04-03T18:56:07.678Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -219,6 +568,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload-time = "2024-08-06T20:32:43.4Z" }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload-time = "2024-08-06T20:32:44.801Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload-time = "2024-08-06T20:32:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload-time = "2024-08-06T20:32:51.188Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload-time = "2024-08-06T20:32:53.019Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload-time = "2024-08-06T20:32:54.708Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload-time = "2024-08-06T20:32:56.985Z" }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload-time = "2024-08-06T20:33:03.001Z" }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, +] + +[[package]] +name = "rich" +version = "14.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, +] + +[[package]] +name = "ruff" +version = "0.11.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/4c/4a3c5a97faaae6b428b336dcca81d03ad04779f8072c267ad2bd860126bf/ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6", size = 4165632, upload-time = "2025-05-15T14:08:56.76Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/9f/596c628f8824a2ce4cd12b0f0b4c0629a62dfffc5d0f742c19a1d71be108/ruff-0.11.10-py3-none-linux_armv6l.whl", hash = "sha256:859a7bfa7bc8888abbea31ef8a2b411714e6a80f0d173c2a82f9041ed6b50f58", size = 10316243, upload-time = "2025-05-15T14:08:12.884Z" }, + { url = "https://files.pythonhosted.org/packages/3c/38/c1e0b77ab58b426f8c332c1d1d3432d9fc9a9ea622806e208220cb133c9e/ruff-0.11.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:968220a57e09ea5e4fd48ed1c646419961a0570727c7e069842edd018ee8afed", size = 11083636, upload-time = "2025-05-15T14:08:16.551Z" }, + { url = "https://files.pythonhosted.org/packages/23/41/b75e15961d6047d7fe1b13886e56e8413be8467a4e1be0a07f3b303cd65a/ruff-0.11.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1067245bad978e7aa7b22f67113ecc6eb241dca0d9b696144256c3a879663bca", size = 10441624, upload-time = "2025-05-15T14:08:19.032Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2c/e396b6703f131406db1811ea3d746f29d91b41bbd43ad572fea30da1435d/ruff-0.11.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4854fd09c7aed5b1590e996a81aeff0c9ff51378b084eb5a0b9cd9518e6cff2", size = 10624358, upload-time = "2025-05-15T14:08:21.542Z" }, + { url = "https://files.pythonhosted.org/packages/bd/8c/ee6cca8bdaf0f9a3704796022851a33cd37d1340bceaf4f6e991eb164e2e/ruff-0.11.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8b4564e9f99168c0f9195a0fd5fa5928004b33b377137f978055e40008a082c5", size = 10176850, upload-time = "2025-05-15T14:08:23.682Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ce/4e27e131a434321b3b7c66512c3ee7505b446eb1c8a80777c023f7e876e6/ruff-0.11.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b6a9cc5b62c03cc1fea0044ed8576379dbaf751d5503d718c973d5418483641", size = 11759787, upload-time = "2025-05-15T14:08:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/58/de/1e2e77fc72adc7cf5b5123fd04a59ed329651d3eab9825674a9e640b100b/ruff-0.11.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:607ecbb6f03e44c9e0a93aedacb17b4eb4f3563d00e8b474298a201622677947", size = 12430479, upload-time = "2025-05-15T14:08:28.013Z" }, + { url = "https://files.pythonhosted.org/packages/07/ed/af0f2340f33b70d50121628ef175523cc4c37619e98d98748c85764c8d88/ruff-0.11.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7b3a522fa389402cd2137df9ddefe848f727250535c70dafa840badffb56b7a4", size = 11919760, upload-time = "2025-05-15T14:08:30.956Z" }, + { url = "https://files.pythonhosted.org/packages/24/09/d7b3d3226d535cb89234390f418d10e00a157b6c4a06dfbe723e9322cb7d/ruff-0.11.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f071b0deed7e9245d5820dac235cbdd4ef99d7b12ff04c330a241ad3534319f", size = 14041747, upload-time = "2025-05-15T14:08:33.297Z" }, + { url = "https://files.pythonhosted.org/packages/62/b3/a63b4e91850e3f47f78795e6630ee9266cb6963de8f0191600289c2bb8f4/ruff-0.11.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a60e3a0a617eafba1f2e4186d827759d65348fa53708ca547e384db28406a0b", size = 11550657, upload-time = "2025-05-15T14:08:35.639Z" }, + { url = "https://files.pythonhosted.org/packages/46/63/a4f95c241d79402ccdbdb1d823d156c89fbb36ebfc4289dce092e6c0aa8f/ruff-0.11.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:da8ec977eaa4b7bf75470fb575bea2cb41a0e07c7ea9d5a0a97d13dbca697bf2", size = 10489671, upload-time = "2025-05-15T14:08:38.437Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9b/c2238bfebf1e473495659c523d50b1685258b6345d5ab0b418ca3f010cd7/ruff-0.11.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ddf8967e08227d1bd95cc0851ef80d2ad9c7c0c5aab1eba31db49cf0a7b99523", size = 10160135, upload-time = "2025-05-15T14:08:41.247Z" }, + { url = "https://files.pythonhosted.org/packages/ba/ef/ba7251dd15206688dbfba7d413c0312e94df3b31b08f5d695580b755a899/ruff-0.11.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5a94acf798a82db188f6f36575d80609072b032105d114b0f98661e1679c9125", size = 11170179, upload-time = "2025-05-15T14:08:43.762Z" }, + { url = "https://files.pythonhosted.org/packages/73/9f/5c336717293203ba275dbfa2ea16e49b29a9fd9a0ea8b6febfc17e133577/ruff-0.11.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3afead355f1d16d95630df28d4ba17fb2cb9c8dfac8d21ced14984121f639bad", size = 11626021, upload-time = "2025-05-15T14:08:46.451Z" }, + { url = "https://files.pythonhosted.org/packages/d9/2b/162fa86d2639076667c9aa59196c020dc6d7023ac8f342416c2f5ec4bda0/ruff-0.11.10-py3-none-win32.whl", hash = "sha256:dc061a98d32a97211af7e7f3fa1d4ca2fcf919fb96c28f39551f35fc55bdbc19", size = 10494958, upload-time = "2025-05-15T14:08:49.601Z" }, + { url = "https://files.pythonhosted.org/packages/24/f3/66643d8f32f50a4b0d09a4832b7d919145ee2b944d43e604fbd7c144d175/ruff-0.11.10-py3-none-win_amd64.whl", hash = "sha256:5cc725fbb4d25b0f185cb42df07ab6b76c4489b4bfb740a175f3a59c70e8a224", size = 11650285, upload-time = "2025-05-15T14:08:52.392Z" }, + { url = "https://files.pythonhosted.org/packages/95/3a/2e8704d19f376c799748ff9cb041225c1d59f3e7711bc5596c8cfdc24925/ruff-0.11.10-py3-none-win_arm64.whl", hash = "sha256:ef69637b35fb8b210743926778d0e45e1bffa850a7c61e428c6b971549b5f5d1", size = 10765278, upload-time = "2025-05-15T14:08:54.56Z" }, +] + +[[package]] +name = "setuptools" +version = "80.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/8b/dc1773e8e5d07fd27c1632c45c1de856ac3dbf09c0147f782ca6d990cf15/setuptools-80.7.1.tar.gz", hash = "sha256:f6ffc5f0142b1bd8d0ca94ee91b30c0ca862ffd50826da1ea85258a06fd94552", size = 1319188, upload-time = "2025-05-15T02:41:00.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/18/0e835c3a557dc5faffc8f91092f62fc337c1dab1066715842e7a4b318ec4/setuptools-80.7.1-py3-none-any.whl", hash = "sha256:ca5cc1069b85dc23070a6628e6bcecb3292acac802399c7f8edc0100619f9009", size = 1200776, upload-time = "2025-05-15T02:40:58.887Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -237,6 +650,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415, upload-time = "2024-12-10T12:05:27.824Z" }, ] +[[package]] +name = "stevedore" +version = "5.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pbr" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/28/3f/13cacea96900bbd31bb05c6b74135f85d15564fc583802be56976c940470/stevedore-5.4.1.tar.gz", hash = "sha256:3135b5ae50fe12816ef291baff420acb727fcd356106e3e9cbfa9e5985cd6f4b", size = 513858, upload-time = "2025-02-20T14:03:57.285Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/45/8c4ebc0c460e6ec38e62ab245ad3c7fc10b210116cea7c16d61602aa9558/stevedore-5.4.1-py3-none-any.whl", hash = "sha256:d10a31c7b86cba16c1f6e8d15416955fc797052351a56af15e608ad20811fcfe", size = 49533, upload-time = "2025-02-20T14:03:55.849Z" }, +] + +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250516" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + [[package]] name = "tzdata" version = "2025.2" @@ -246,6 +689,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, ] +[[package]] +name = "virtualenv" +version = "20.31.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, +] + [[package]] name = "whitenoise" version = "6.9.0"