chore(deps): update pre-commit config and apply bulk formatting

- build(pre-commit): upgrade hooks (django-upgrade 1.29.1, uv 0.9.7, ruff 0.14.3, bandit 1.8.6)
- build(pre-commit): add uv-lock hook, tombi TOML formatter, prettier-plugin-packagejson
- build(pre-commit): disable Django check hooks (commented out)
- build(pre-commit): switch npx → bunx for prettier execution
- build(node): add bun.lock, update prettier config with schema + packagejson plugin
- style: apply ruff format to all Python files (comments, spacing, imports)
- style: apply prettier format to all JS/CSS files (comment styles, spacing)
- style: apply tombi format to pyproject.toml (reordered sections, consistent formatting)
- chore: remove emoji from bash script comments for consistency

BREAKING CHANGE: Django check and migration check hooks disabled in pre-commit config
This commit is contained in:
2025-11-05 14:34:08 +01:00
parent b1b5207888
commit c106792e78
35 changed files with 615 additions and 328 deletions

View File

@@ -13,21 +13,25 @@ default_language_version:
repos: repos:
- repo: https://github.com/adamchainz/django-upgrade - repo: https://github.com/adamchainz/django-upgrade
rev: 1.25.0 rev: 1.29.1
hooks: hooks:
- id: django-upgrade - id: django-upgrade
# uv hooks for dependency management # uv hooks for dependency management
- repo: https://github.com/astral-sh/uv-pre-commit - repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.7.12 rev: 0.9.7
hooks: hooks:
# Update the uv lockfile
- id: uv-lock
# Update the requirements.txt
- id: uv-export - id: uv-export
# Standard pre-commit hooks # Standard pre-commit hooks
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0 rev: v6.0.0
hooks: hooks:
- id: trailing-whitespace - id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
- id: end-of-file-fixer - id: end-of-file-fixer
- id: check-yaml - id: check-yaml
# - id: check-json # - id: check-json
@@ -50,12 +54,12 @@ repos:
- prettier - prettier
- prettier-plugin-jinja-template - prettier-plugin-jinja-template
types_or: [html, jinja] types_or: [html, jinja]
entry: npx prettier --plugin=prettier-plugin-jinja-template --parser=jinja-template --write entry: bunx prettier --plugin=prettier-plugin-jinja-template --parser=jinja-template --write
- id: prettier-all - id: prettier-all
name: Prettier All name: Prettier All
language: node language: node
types_or: [javascript, jsx, ts, tsx, css, scss, json, yaml, markdown] types_or: [javascript, jsx, ts, tsx, css, scss, json, yaml, markdown]
entry: npx prettier --write entry: bunx prettier --write
- repo: https://github.com/DavidAnson/markdownlint-cli2 - repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.18.1 rev: v0.18.1
@@ -65,40 +69,49 @@ repos:
# Ruff for linting and formatting # Ruff for linting and formatting
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.13 rev: v0.14.3
hooks: hooks:
- id: ruff # Run the linter.
- id: ruff-check
args: [--fix] args: [--fix]
# Run the formatter.
- id: ruff-format - id: ruff-format
# Django-specific hooks # # Django-specific hooks
- repo: local # - repo: local
hooks: # hooks:
- id: django-check # - id: django-check
name: Django Check # name: Django Check
entry: uv run python dashboard_project/manage.py check # entry: uv run python dashboard_project/manage.py check
language: python # language: python
pass_filenames: false # pass_filenames: false
types: [python] # types: [python]
always_run: true # always_run: true
additional_dependencies: [uv] # additional_dependencies: [uv]
- id: django-check-migrations # - id: django-check-migrations
name: Django Check Migrations # name: Django Check Migrations
entry: uv run python dashboard_project/manage.py makemigrations --check --dry-run # entry: uv run python dashboard_project/manage.py makemigrations --check --dry-run
language: python # language: python
pass_filenames: false # pass_filenames: false
types: [python] # types: [python]
additional_dependencies: [uv] # additional_dependencies: [uv]
# Security checks # Security checks
- repo: https://github.com/pycqa/bandit - repo: https://github.com/pycqa/bandit
rev: 1.8.3 rev: 1.8.6
hooks: hooks:
- id: bandit - id: bandit
args: [-c, pyproject.toml, -r, dashboard_project] args: [-c, pyproject.toml, -r, dashboard_project]
# additional_dependencies: ["bandit[toml]"] # additional_dependencies: ["bandit[toml]"]
# TOML formatting and linting
- repo: https://github.com/tombi-toml/tombi-pre-commit
rev: v0.6.40
hooks:
- id: tombi-format
- id: tombi-lint
# # Type checking # # Type checking
# - repo: https://github.com/pre-commit/mirrors-mypy # - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.15.0 # rev: v1.15.0

View File

@@ -1,4 +1,5 @@
{ {
"$schema": "https://json.schemastore.org/prettierrc.json",
"arrowParens": "always", "arrowParens": "always",
"bracketSpacing": true, "bracketSpacing": true,
"embeddedLanguageFormatting": "auto", "embeddedLanguageFormatting": "auto",
@@ -29,5 +30,5 @@
} }
} }
], ],
"plugins": ["prettier-plugin-jinja-template"] "plugins": ["prettier-plugin-jinja-template", "prettier-plugin-packagejson"]
} }

View File

@@ -3,32 +3,41 @@
FROM python:3.13-slim FROM python:3.13-slim
# Set environment variables # Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV DJANGO_SETTINGS_MODULE=dashboard_project.settings ENV DJANGO_SETTINGS_MODULE=dashboard_project.settings
# Set work directory # Set work directory
WORKDIR /app WORKDIR /app
# Install UV for Python package management # Install UV for Python package management
RUN pip install uv RUN pip install uv
# Copy project files # Copy project files
COPY pyproject.toml . COPY pyproject.toml .
COPY uv.lock . COPY uv.lock .
COPY . . COPY . .
# Install dependencies # Install dependencies
RUN uv pip install -e . RUN uv pip install -e .
# Change to the Django project directory # Change to the Django project directory
WORKDIR /app/dashboard_project WORKDIR /app/dashboard_project
# Collect static files # Collect static files
RUN python manage.py collectstatic --noinput RUN python manage.py collectstatic --noinput
# Change back to the app directory # Change back to the app directory
WORKDIR /app WORKDIR /app
# Run gunicorn # Run gunicorn
CMD ["gunicorn", "dashboard_project.wsgi:application", "--bind", "0.0.0.0:8000"] CMD ["gunicorn", "dashboard_project.wsgi:application", "--bind", "0.0.0.0:8000"]

114
Makefile
View File

@@ -1,124 +1,148 @@
.PHONY: venv install install-dev lint test format clean run migrate makemigrations superuser setup-node celery celery-beat docker-build docker-up docker-down reset-db setup-dev procfile .PHONY: venv install install-dev lint test format clean run migrate makemigrations superuser setup-node celery celery-beat docker-build docker-up docker-down reset-db setup-dev procfile
# Create a virtual environment # Create a virtual environment
venv: venv:
uv venv -p 3.13 uv venv -p 3.13
# Install production dependencies # Install production dependencies
install: install:
uv pip install -e . uv pip install -e .
# Install development dependencies # Install development dependencies
install-dev: install-dev:
uv pip install -e ".[dev]" uv pip install -e ".[dev]"
# Run linting # Run linting
lint: lint:
uv run -m ruff check dashboard_project uv run -m ruff check dashboard_project
# Run tests # Run tests
test: test:
uv run -m pytest uv run -m pytest
# Format Python code # Format Python code
format: format:
uv run -m ruff format dashboard_project uv run -m ruff format dashboard_project
uv run -m black dashboard_project uv run -m black dashboard_project
# Setup Node.js dependencies # Setup Node.js dependencies
setup-node: setup-node:
npm install --include=dev npm install --include=dev
# Clean Python cache files # Clean Python cache files
clean: clean:
find . -type d -name "__pycache__" -exec rm -rf {} + find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -delete find . -type f -name "*.pyc" -delete
find . -type f -name "*.pyo" -delete find . -type f -name "*.pyo" -delete
find . -type f -name "*.pyd" -delete find . -type f -name "*.pyd" -delete
find . -type d -name "*.egg-info" -exec rm -rf {} + find . -type d -name "*.egg-info" -exec rm -rf {} +
find . -type d -name "*.egg" -exec rm -rf {} + find . -type d -name "*.egg" -exec rm -rf {} +
find . -type d -name ".pytest_cache" -exec rm -rf {} + find . -type d -name ".pytest_cache" -exec rm -rf {} +
find . -type d -name ".coverage" -exec rm -rf {} + find . -type d -name ".coverage" -exec rm -rf {} +
find . -type d -name "htmlcov" -exec rm -rf {} + find . -type d -name "htmlcov" -exec rm -rf {} +
find . -type d -name ".ruff_cache" -exec rm -rf {} + find . -type d -name ".ruff_cache" -exec rm -rf {} +
find . -type d -name ".mypy_cache" -exec rm -rf {} + find . -type d -name ".mypy_cache" -exec rm -rf {} +
find . -type d -name ".tox" -exec rm -rf {} + find . -type d -name ".tox" -exec rm -rf {} +
find . -type d -name "node_modules" -exec rm -rf {} + find . -type d -name "node_modules" -exec rm -rf {} +
rm -rf build/ rm -rf build/
rm -rf dist/ rm -rf dist/
# Run the development server # Run the development server
run: run:
cd dashboard_project && uv run python manage.py runserver 8001 cd dashboard_project && uv run python manage.py runserver 8001
# Run Celery worker for background tasks # Run Celery worker for background tasks
celery: celery:
cd dashboard_project && uv run celery -A dashboard_project worker --loglevel=info cd dashboard_project && uv run celery -A dashboard_project worker --loglevel=info
# Run Celery Beat for scheduled tasks # Run Celery Beat for scheduled tasks
celery-beat: celery-beat:
cd dashboard_project && uv run celery -A dashboard_project beat --scheduler django_celery_beat.schedulers:DatabaseScheduler cd dashboard_project && uv run celery -A dashboard_project beat --scheduler django_celery_beat.schedulers:DatabaseScheduler
# Apply migrations # Apply migrations
migrate: migrate:
cd dashboard_project && uv run python manage.py migrate cd dashboard_project && uv run python manage.py migrate
# Create migrations # Create migrations
makemigrations: makemigrations:
cd dashboard_project && uv run python manage.py makemigrations cd dashboard_project && uv run python manage.py makemigrations
# Create a superuser # Create a superuser
superuser: superuser:
cd dashboard_project && uv run python manage.py createsuperuser cd dashboard_project && uv run python manage.py createsuperuser
# Update uv lock file # Update uv lock file
lock: lock:
uv pip freeze > requirements.lock uv pip freeze > requirements.lock
# Setup pre-commit hooks # Setup pre-commit hooks
setup-pre-commit: setup-pre-commit:
pre-commit install pre-commit install
# Run pre-commit on all files # Run pre-commit on all files
lint-all: lint-all:
pre-commit run --all-files pre-commit run --all-files
# Docker commands # Docker commands
docker-build: docker-build:
docker-compose build docker-compose build
docker-up: docker-up:
docker-compose up -d docker-compose up -d
docker-down: docker-down:
docker-compose down docker-compose down
# Initialize or reset the database in development # Initialize or reset the database in development
reset-db: reset-db:
cd dashboard_project && uv run python manage.py flush --no-input cd dashboard_project && uv run python manage.py flush --no-input
cd dashboard_project && uv run python manage.py migrate cd dashboard_project && uv run python manage.py migrate
# Start a Redis server in development (if not installed, fallback to SQLite) # Start a Redis server in development (if not installed, fallback to SQLite)
run-redis: run-redis:
redis-server || echo "Redis not installed, using SQLite fallback" redis-server || echo "Redis not installed, using SQLite fallback"
# Start all development services (web, redis, celery, celery-beat) # Start all development services (web, redis, celery, celery-beat)
run-all: run-all:
foreman start foreman start
procfile: procfile:
foreman start foreman start
# Test Celery task # Test Celery task
test-celery: test-celery:
cd dashboard_project && uv run python manage.py test_celery cd dashboard_project && uv run python manage.py test_celery
# Initialize data integration # Initialize data integration
init-data-integration: init-data-integration:
cd dashboard_project && uv run python manage.py create_default_datasource cd dashboard_project && uv run python manage.py create_default_datasource
cd dashboard_project && uv run python manage.py create_default_datasource cd dashboard_project && uv run python manage.py create_default_datasource
cd dashboard_project && uv run python manage.py test_celery cd dashboard_project && uv run python manage.py test_celery
# Setup development environment # Setup development environment
setup-dev: venv install-dev migrate create_default_datasource setup-dev: venv install-dev migrate create_default_datasource
@echo "Development environment setup complete" @echo "Development environment setup complete"

204
bun.lock Normal file
View File

@@ -0,0 +1,204 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"devDependencies": {
"markdownlint-cli2": "^0.18.1",
"prettier": "^3.6.2",
"prettier-plugin-jinja-template": "^2.1.0",
"prettier-plugin-packagejson": "^2.5.19",
},
},
},
"packages": {
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@2.3.0", "", {}, "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
"commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"detect-indent": ["detect-indent@7.0.2", "", {}, "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A=="],
"detect-newline": ["detect-newline@4.0.1", "", {}, "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
"git-hooks-list": ["git-hooks-list@4.1.1", "", {}, "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA=="],
"glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"globby": ["globby@14.1.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", "ignore": "^7.0.3", "path-type": "^6.0.0", "slash": "^5.1.0", "unicorn-magic": "^0.3.0" } }, "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA=="],
"ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
"is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
"is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
"jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
"katex": ["katex@0.16.25", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q=="],
"linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="],
"markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="],
"markdownlint": ["markdownlint@0.38.0", "", { "dependencies": { "micromark": "4.0.2", "micromark-core-commonmark": "2.0.3", "micromark-extension-directive": "4.0.0", "micromark-extension-gfm-autolink-literal": "2.1.0", "micromark-extension-gfm-footnote": "2.1.0", "micromark-extension-gfm-table": "2.1.1", "micromark-extension-math": "3.1.0", "micromark-util-types": "2.0.2" } }, "sha512-xaSxkaU7wY/0852zGApM8LdlIfGCW8ETZ0Rr62IQtAnUMlMuifsg09vWJcNYeL4f0anvr8Vo4ZQar8jGpV0btQ=="],
"markdownlint-cli2": ["markdownlint-cli2@0.18.1", "", { "dependencies": { "globby": "14.1.0", "js-yaml": "4.1.0", "jsonc-parser": "3.3.1", "markdown-it": "14.1.0", "markdownlint": "0.38.0", "markdownlint-cli2-formatter-default": "0.0.5", "micromatch": "4.0.8" }, "bin": { "markdownlint-cli2": "markdownlint-cli2-bin.mjs" } }, "sha512-/4Osri9QFGCZOCTkfA8qJF+XGjKYERSHkXzxSyS1hd3ZERJGjvsUao2h4wdnvpHp6Tu2Jh/bPHM0FE9JJza6ng=="],
"markdownlint-cli2-formatter-default": ["markdownlint-cli2-formatter-default@0.0.5", "", { "peerDependencies": { "markdownlint-cli2": ">=0.0.4" } }, "sha512-4XKTwQ5m1+Txo2kuQ3Jgpo/KmnG+X90dWt4acufg6HVGadTUG5hzHF/wssp9b5MBYOMCnZ9RMPaU//uHsszF8Q=="],
"mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
"micromark-extension-directive": ["micromark-extension-directive@4.0.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "parse-entities": "^4.0.0" } }, "sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg=="],
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
"micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "dependencies": { "@types/katex": "^0.16.0", "devlop": "^1.0.0", "katex": "^0.16.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="],
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
"path-type": ["path-type@6.0.0", "", {}, "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ=="],
"picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
"prettier-plugin-jinja-template": ["prettier-plugin-jinja-template@2.1.0", "", { "peerDependencies": { "prettier": "^3.0.0" } }, "sha512-mzoCp2Oy9BDSug80fw3B3J4n4KQj1hRvoQOL1akqcDKBb5nvYxrik9zUEDs4AEJ6nK7QDTGoH0y9rx7AlnQ78Q=="],
"prettier-plugin-packagejson": ["prettier-plugin-packagejson@2.5.19", "", { "dependencies": { "sort-package-json": "3.4.0", "synckit": "0.11.11" }, "peerDependencies": { "prettier": ">= 1.16.0" }, "optionalPeers": ["prettier"] }, "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag=="],
"punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"slash": ["slash@5.1.0", "", {}, "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg=="],
"sort-object-keys": ["sort-object-keys@1.1.3", "", {}, "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg=="],
"sort-package-json": ["sort-package-json@3.4.0", "", { "dependencies": { "detect-indent": "^7.0.1", "detect-newline": "^4.0.1", "git-hooks-list": "^4.0.0", "is-plain-obj": "^4.1.0", "semver": "^7.7.1", "sort-object-keys": "^1.1.3", "tinyglobby": "^0.2.12" }, "bin": { "sort-package-json": "cli.js" } }, "sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA=="],
"synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
"uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="],
"unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
}
}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python # !/usr/bin/env python
""" """
Entry point for Django commands executed as Python modules. Entry point for Django commands executed as Python modules.
This enables commands like `python -m runserver`. This enables commands like `python -m runserver`.

View File

@@ -4,7 +4,7 @@ ASGI config for dashboard_project project.
It exposes the ASGI callable as a module-level variable named ``application``. It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ <https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/>
""" """
import os import os

View File

@@ -1,2 +1,3 @@
# dashboard/management/__init__.py # dashboard/management/__init__.py
# This file is intentionally left empty to mark the directory as a Python package # This file is intentionally left empty to mark the directory as a Python package

View File

@@ -1,2 +1,3 @@
# dashboard/management/commands/__init__.py # dashboard/management/commands/__init__.py
# This file is intentionally left empty to mark the directory as a Python package # This file is intentionally left empty to mark the directory as a Python package

View File

@@ -1,2 +1,3 @@
# dashboard/templatetags/__init__.py # dashboard/templatetags/__init__.py
# This file is intentionally left empty to mark the directory as a Python package # This file is intentionally left empty to mark the directory as a Python package

View File

@@ -200,12 +200,10 @@ def chat_session_detail_view(request, session_id):
# Check if this is an AJAX navigation request # Check if this is an AJAX navigation request
if is_ajax_navigation(request): if is_ajax_navigation(request):
html_content = render_to_string("dashboard/chat_session_detail.html", context, request=request) html_content = render_to_string("dashboard/chat_session_detail.html", context, request=request)
return JsonResponse( return JsonResponse({
{ "html": html_content,
"html": html_content, "title": f"Chat Session {session_id} | Chat Analytics",
"title": f"Chat Session {session_id} | Chat Analytics", })
}
)
return render(request, "dashboard/chat_session_detail.html", context) return render(request, "dashboard/chat_session_detail.html", context)
@@ -282,12 +280,10 @@ def edit_dashboard_view(request, dashboard_id):
# Check if this is an AJAX navigation request # Check if this is an AJAX navigation request
if is_ajax_navigation(request): if is_ajax_navigation(request):
html_content = render_to_string("dashboard/dashboard_form.html", context, request=request) html_content = render_to_string("dashboard/dashboard_form.html", context, request=request)
return JsonResponse( return JsonResponse({
{ "html": html_content,
"html": html_content, "title": f"Edit Dashboard: {dashboard.name} | Chat Analytics",
"title": f"Edit Dashboard: {dashboard.name} | Chat Analytics", })
}
)
return render(request, "dashboard/dashboard_form.html", context) return render(request, "dashboard/dashboard_form.html", context)
@@ -349,6 +345,8 @@ def delete_data_source_view(request, data_source_id):
# API views for dashboard data # API views for dashboard data
@login_required @login_required
def dashboard_data_api(request, dashboard_id): def dashboard_data_api(request, dashboard_id):
"""API endpoint for dashboard data""" """API endpoint for dashboard data"""
@@ -450,26 +448,24 @@ def search_chat_sessions(request):
# Check if this is an AJAX pagination request # Check if this is an AJAX pagination request
if request.headers.get("X-Requested-With") == "XMLHttpRequest": if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return JsonResponse( return JsonResponse({
{ "status": "success",
"status": "success", "html_data": render(request, "dashboard/partials/search_results_table.html", context).content.decode(
"html_data": render(request, "dashboard/partials/search_results_table.html", context).content.decode( "utf-8"
"utf-8" ),
), "page_obj": {
"page_obj": { "number": page_obj.number,
"number": page_obj.number, "has_previous": page_obj.has_previous(),
"has_previous": page_obj.has_previous(), "has_next": page_obj.has_next(),
"has_next": page_obj.has_next(), "previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None, "next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None, "paginator": {
"paginator": { "num_pages": page_obj.paginator.num_pages,
"num_pages": page_obj.paginator.num_pages, "count": page_obj.paginator.count,
"count": page_obj.paginator.count,
},
}, },
"query": query, },
} "query": query,
) })
return render(request, "dashboard/search_results.html", context) return render(request, "dashboard/search_results.html", context)
@@ -554,26 +550,24 @@ def data_view(request):
# Check if this is an AJAX pagination request # Check if this is an AJAX pagination request
if request.headers.get("X-Requested-With") == "XMLHttpRequest": if request.headers.get("X-Requested-With") == "XMLHttpRequest":
return JsonResponse( return JsonResponse({
{ "status": "success",
"status": "success", "html_data": render(request, "dashboard/partials/data_table.html", context).content.decode("utf-8"),
"html_data": render(request, "dashboard/partials/data_table.html", context).content.decode("utf-8"), "page_obj": {
"page_obj": { "number": page_obj.number,
"number": page_obj.number, "has_previous": page_obj.has_previous(),
"has_previous": page_obj.has_previous(), "has_next": page_obj.has_next(),
"has_next": page_obj.has_next(), "previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None,
"previous_page_number": page_obj.previous_page_number() if page_obj.has_previous() else None, "next_page_number": page_obj.next_page_number() if page_obj.has_next() else None,
"next_page_number": page_obj.next_page_number() if page_obj.has_next() else None, "paginator": {
"paginator": { "num_pages": page_obj.paginator.num_pages,
"num_pages": page_obj.paginator.num_pages, "count": page_obj.paginator.count,
"count": page_obj.paginator.count,
},
}, },
"view": view, },
"avg_response_time": avg_response_time, "view": view,
"avg_messages": avg_messages, "avg_response_time": avg_response_time,
"escalation_rate": escalation_rate, "avg_messages": avg_messages,
} "escalation_rate": escalation_rate,
) })
return render(request, "dashboard/data_view.html", context) return render(request, "dashboard/data_view.html", context)

View File

@@ -91,51 +91,47 @@ def export_chats_csv(request):
writer = csv.writer(response) writer = csv.writer(response)
# Write CSV header # Write CSV header
writer.writerow( writer.writerow([
[ "Session ID",
"Session ID", "Start Time",
"Start Time", "End Time",
"End Time", "IP Address",
"IP Address", "Country",
"Country", "Language",
"Language", "Messages Sent",
"Messages Sent", "Sentiment",
"Sentiment", "Escalated",
"Escalated", "Forwarded HR",
"Forwarded HR", "Full Transcript",
"Full Transcript", "Avg Response Time (s)",
"Avg Response Time (s)", "Tokens",
"Tokens", "Tokens EUR",
"Tokens EUR", "Category",
"Category", "Initial Message",
"Initial Message", "User Rating",
"User Rating", ])
]
)
# Write data rows # Write data rows
for session in sessions: for session in sessions:
writer.writerow( writer.writerow([
[ session.session_id,
session.session_id, session.start_time,
session.start_time, session.end_time,
session.end_time, session.ip_address,
session.ip_address, session.country,
session.country, session.language,
session.language, session.messages_sent,
session.messages_sent, session.sentiment,
session.sentiment, "Yes" if session.escalated else "No",
"Yes" if session.escalated else "No", "Yes" if session.forwarded_hr else "No",
"Yes" if session.forwarded_hr else "No", session.full_transcript,
session.full_transcript, session.avg_response_time,
session.avg_response_time, session.tokens,
session.tokens, session.tokens_eur,
session.tokens_eur, session.category,
session.category, session.initial_msg,
session.initial_msg, session.user_rating,
session.user_rating, ])
]
)
return response return response

View File

@@ -4,7 +4,7 @@ ASGI config for dashboard_project project.
It exposes the ASGI callable as a module-level variable named ``application``. It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/ <https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/>
""" """
import os import os

View File

@@ -2,18 +2,24 @@ import os
from celery import Celery from celery import Celery
# Set the default Django settings module for the 'celery' program. # Set the default Django settings module for the 'celery' program
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
app = Celery("dashboard_project") app = Celery("dashboard_project")
# Using a string here means the worker doesn't have to serialize # Using a string here means the worker doesn't have to serialize
# the configuration object to child processes.
# the configuration object to child processes
# - namespace='CELERY' means all celery-related configuration keys # - namespace='CELERY' means all celery-related configuration keys
# should have a `CELERY_` prefix.
# should have a `CELERY_` prefix
app.config_from_object("django.conf:settings", namespace="CELERY") app.config_from_object("django.conf:settings", namespace="CELERY")
# Load task modules from all registered Django app configs. # Load task modules from all registered Django app configs
app.autodiscover_tasks() app.autodiscover_tasks()

View File

@@ -7,6 +7,7 @@ from pathlib import Path
from django.core.management.utils import get_random_secret_key from django.core.management.utils import get_random_secret_key
# Load environment variables from .env file if present # Load environment variables from .env file if present
try: try:
from dotenv import load_dotenv from dotenv import load_dotenv
@@ -14,18 +15,22 @@ try:
except ImportError: except ImportError:
pass pass
# Build paths inside the project like this: BASE_DIR / 'subdir'. # Build paths inside the project like this: BASE_DIR / 'subdir'
BASE_DIR = Path(__file__).resolve().parent.parent BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", get_random_secret_key()) SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", get_random_secret_key())
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production
DEBUG = os.environ.get("DJANGO_DEBUG", "True") == "True" DEBUG = os.environ.get("DJANGO_DEBUG", "True") == "True"
ALLOWED_HOSTS = [] ALLOWED_HOSTS = []
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"django.contrib.admin", "django.contrib.admin",
"django.contrib.auth", "django.contrib.auth",
@@ -80,6 +85,7 @@ TEMPLATES = [
WSGI_APPLICATION = "dashboard_project.wsgi.application" WSGI_APPLICATION = "dashboard_project.wsgi.application"
# Database # Database
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.sqlite3", "ENGINE": "django.db.backends.sqlite3",
@@ -88,6 +94,7 @@ DATABASES = {
} }
# Password validation # Password validation
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@@ -104,12 +111,14 @@ AUTH_PASSWORD_VALIDATORS = [
] ]
# Internationalization # Internationalization
LANGUAGE_CODE = "en-US" LANGUAGE_CODE = "en-US"
TIME_ZONE = "Europe/Amsterdam" TIME_ZONE = "Europe/Amsterdam"
USE_I18N = True USE_I18N = True
USE_TZ = True USE_TZ = True
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
STATIC_URL = "static/" STATIC_URL = "static/"
STATICFILES_DIRS = [ STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static"), os.path.join(BASE_DIR, "static"),
@@ -125,23 +134,28 @@ STORAGES = {
} }
# Media files # Media files
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# Default primary key field type # Default primary key field type
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Crispy Forms # Crispy Forms
CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5"
CRISPY_TEMPLATE_PACK = "bootstrap5" CRISPY_TEMPLATE_PACK = "bootstrap5"
# Authentication # Authentication
AUTH_USER_MODEL = "accounts.CustomUser" AUTH_USER_MODEL = "accounts.CustomUser"
LOGIN_REDIRECT_URL = "dashboard" LOGIN_REDIRECT_URL = "dashboard"
LOGOUT_REDIRECT_URL = "login" LOGOUT_REDIRECT_URL = "login"
ACCOUNT_LOGOUT_ON_GET = True ACCOUNT_LOGOUT_ON_GET = True
# django-allauth # django-allauth
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend", "django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend", "allauth.account.auth_backends.AuthenticationBackend",
@@ -150,7 +164,9 @@ SITE_ID = 1
ACCOUNT_EMAIL_VERIFICATION = "none" ACCOUNT_EMAIL_VERIFICATION = "none"
# Celery Configuration # Celery Configuration
# Check if Redis is available # Check if Redis is available
try: try:
import redis import redis
@@ -184,6 +200,7 @@ CELERY_TIMEZONE = TIME_ZONE
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"
# Get schedule from environment variables or use defaults # Get schedule from environment variables or use defaults
CHAT_DATA_FETCH_INTERVAL = int(os.environ.get("CHAT_DATA_FETCH_INTERVAL", 3600)) # Default: 1 hour CHAT_DATA_FETCH_INTERVAL = int(os.environ.get("CHAT_DATA_FETCH_INTERVAL", 3600)) # Default: 1 hour
CELERY_BEAT_SCHEDULE = { CELERY_BEAT_SCHEDULE = {

View File

@@ -4,7 +4,7 @@ WSGI config for dashboard_project project.
It exposes the WSGI callable as a module-level variable named ``application``. It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ <https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/>
""" """
import os import os

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python # !/usr/bin/env python
""" """
Migration Fix Script for ExternalDataSource Migration Fix Script for ExternalDataSource

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python # !/usr/bin/env python
""" """
Test the ExternalDataSource Model Schema Test the ExternalDataSource Model Schema

View File

@@ -39,7 +39,7 @@ class ChatMessage(models.Model):
class ExternalDataSource(models.Model): class ExternalDataSource(models.Model):
name = models.CharField(max_length=255, default="External API") name = models.CharField(max_length=255, default="External API")
api_url = models.URLField(default="https://proto.notso.ai/jumbo/chats") api_url = models.URLField(default="<https://proto.notso.ai/jumbo/chats>")
auth_username = models.CharField(max_length=255, blank=True, null=True) auth_username = models.CharField(max_length=255, blank=True, null=True)
auth_password = models.CharField( auth_password = models.CharField(
max_length=255, blank=True, null=True max_length=255, blank=True, null=True

View File

@@ -1 +1 @@
# Create your tests here. # Create your tests here

View File

@@ -7,7 +7,7 @@ from .models import ExternalDataSource
from .tasks import periodic_fetch_chat_data, refresh_specific_source from .tasks import periodic_fetch_chat_data, refresh_specific_source
from .utils import fetch_and_store_chat_data from .utils import fetch_and_store_chat_data
# Create your views here. # Create your views here
def is_superuser(user): def is_superuser(user):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python # !/usr/bin/env python
"""Django's command-line utility for administrative tasks.""" """Django's command-line utility for administrative tasks."""
import os import os

View File

@@ -4,6 +4,7 @@ import os
import sys import sys
# Add the project root to sys.path # Add the project root to sys.path
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python # !/usr/bin/env python
# scripts/fix_dashboard_data.py # scripts/fix_dashboard_data.py
import os import os
@@ -15,11 +16,13 @@ from django.db import transaction
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
# Set up Django environment # Set up Django environment
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dashboard_project.settings")
django.setup() django.setup()
# SCRIPT CONFIG # SCRIPT CONFIG
CREATE_TEST_DATA = False # Set to True to create sample data if none exists CREATE_TEST_DATA = False # Set to True to create sample data if none exists
COMPANY_NAME = "Notso AI" # The company name to use COMPANY_NAME = "Notso AI" # The company name to use

View File

@@ -1,10 +1,11 @@
/** /**
* dashboard.css - Styles specific to dashboard functionality
* dashboard.css - Styles specific to dashboard functionality
*/ */
/* Theme variables */ /*Theme variables */
:root { :root {
/* Light theme (default) */ /* Light theme (default)*/
--bg-color: #f8f9fa; --bg-color: #f8f9fa;
--text-color: #212529; --text-color: #212529;
--card-bg: #ffffff; --card-bg: #ffffff;
@@ -26,7 +27,7 @@
color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
} }
/* Dark theme */ /*Dark theme*/
[data-bs-theme="dark"] { [data-bs-theme="dark"] {
--bg-color: #212529; --bg-color: #212529;
--text-color: #f8f9fa; --text-color: #f8f9fa;
@@ -47,7 +48,7 @@
--icon-color: #6ea8fe; --icon-color: #6ea8fe;
} }
/* Apply theme variables */ /*Apply theme variables*/
body { body {
background-color: var(--bg-color); background-color: var(--bg-color);
color: var(--text-color); color: var(--text-color);
@@ -91,7 +92,7 @@ body {
background-color: var(--sidebar-bg) !important; background-color: var(--sidebar-bg) !important;
} }
/* Sidebar navigation styling with dark mode support */ /*Sidebar navigation styling with dark mode support*/
.sidebar .nav-link { .sidebar .nav-link {
color: var(--text-color); color: var(--text-color);
transition: all 0.2s ease; transition: all 0.2s ease;
@@ -168,7 +169,7 @@ body {
color: var(--text-color); color: var(--text-color);
} }
/* Footer */ /*Footer*/
footer { footer {
background-color: var(--card-bg); background-color: var(--card-bg);
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
@@ -182,7 +183,7 @@ footer {
background-color: var(--navbar-bg); background-color: var(--navbar-bg);
} }
/* Dashboard grid layout */ /*Dashboard grid layout*/
.dashboard-grid { .dashboard-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
@@ -193,7 +194,7 @@ footer {
/* Increased gap */ /* Increased gap */
} }
/* Dashboard widget cards */ /*Dashboard widget cards*/
.dashboard-widget { .dashboard-widget {
display: flex; display: flex;
@@ -254,7 +255,7 @@ footer {
/* Consistent padding */ /* Consistent padding */
} }
/* Chart widgets */ /*Chart widgets*/
.chart-widget .card-body { .chart-widget .card-body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -270,7 +271,7 @@ footer {
/* Ensure it takes full width of card body */ /* Ensure it takes full width of card body */
} }
/* Stat widgets / Stat Cards */ /*Stat widgets / Stat Cards*/
.stat-card { .stat-card {
text-align: center; text-align: center;
padding: 1.5rem; padding: 1.5rem;
@@ -319,7 +320,7 @@ footer {
margin-bottom: 0; margin-bottom: 0;
} }
/* Dashboard theme variations */ /*Dashboard theme variations*/
.dashboard-theme-light .card { .dashboard-theme-light .card {
background-color: #fff; background-color: #fff;
} }
@@ -344,7 +345,7 @@ footer {
color: #adb5bd; color: #adb5bd;
} }
/* Time period selector */ /*Time period selector*/
.time-period-selector { .time-period-selector {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -367,7 +368,7 @@ footer {
font-size: 0.875rem; font-size: 0.875rem;
} }
/* Custom metric selector */ /*Custom metric selector*/
.metric-selector { .metric-selector {
max-width: 100%; max-width: 100%;
overflow-x: auto; overflow-x: auto;
@@ -388,7 +389,7 @@ footer {
border-radius: 0.25rem; border-radius: 0.25rem;
} }
/* Dashboard loading states */ /*Dashboard loading states*/
.widget-placeholder { .widget-placeholder {
min-height: 300px; min-height: 300px;
background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%); background: linear-gradient(90deg, #e9ecef 25%, #f8f9fa 50%, #e9ecef 75%);
@@ -413,7 +414,7 @@ footer {
} }
} }
/* Dashboard empty states */ /*Dashboard empty states*/
.empty-state { .empty-state {
padding: 2.5rem; padding: 2.5rem;
@@ -449,7 +450,7 @@ footer {
margin-top: 1rem; margin-top: 1rem;
} }
/* Responsive adjustments */ /*Responsive adjustments*/
@media (width <=767.98px) { @media (width <=767.98px) {
.dashboard-grid { .dashboard-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@@ -471,7 +472,7 @@ footer {
} }
} }
/* Preserve colored background for stat cards in both themes */ /*Preserve colored background for stat cards in both themes*/
.col-md-3 .card.stats-card.bg-primary { .col-md-3 .card.stats-card.bg-primary {
background-color: var(--bs-primary) !important; background-color: var(--bs-primary) !important;
color: white !important; color: white !important;
@@ -507,7 +508,7 @@ footer {
color: var(--bs-dark) !important; color: var(--bs-dark) !important;
} }
/* Stats Cards Alignment Fix (Bottom Align, No Overlap) */ /*Stats Cards Alignment Fix (Bottom Align, No Overlap)*/
.stats-row { .stats-row {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;

View File

@@ -1,8 +1,9 @@
/** /**
* style.css - Global styles for the application
* style.css - Global styles for the application
*/ */
/* General Styles */ /*General Styles*/
body { body {
font-family: font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif,
@@ -25,14 +26,14 @@ body {
/* Ensures body takes at least full viewport height */ /* Ensures body takes at least full viewport height */
} }
/* Navbar adjustments (if needed, Bootstrap usually handles this well) */ /*Navbar adjustments (if needed, Bootstrap usually handles this well)*/
.navbar { .navbar {
box-shadow: 0 2px 4px rgb(0 0 0 / 5%); box-shadow: 0 2px 4px rgb(0 0 0 / 5%);
/* Subtle shadow for depth */ /* Subtle shadow for depth */
} }
/* Helper Classes */ /*Helper Classes*/
.text-truncate-2 { .text-truncate-2 {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
@@ -49,7 +50,7 @@ body {
min-width: 150px; min-width: 150px;
} }
/* Card styles */ /*Card styles*/
.card { .card {
border: 1px solid #e0e5e9; border: 1px solid #e0e5e9;
@@ -91,7 +92,7 @@ body {
font-weight: 600; font-weight: 600;
} }
/* Sidebar enhancements */ /*Sidebar enhancements*/
.sidebar { .sidebar {
background-color: #fff; background-color: #fff;
@@ -159,7 +160,7 @@ body {
margin-top: 1rem; margin-top: 1rem;
} }
/* Dashboard stats cards */ /*Dashboard stats cards*/
.stats-card { .stats-card {
border-radius: 0.5rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
@@ -176,14 +177,14 @@ body {
opacity: 0.8; opacity: 0.8;
} }
/* Chart containers */ /*Chart containers*/
.chart-container { .chart-container {
width: 100%; width: 100%;
height: 300px; height: 300px;
position: relative; position: relative;
} }
/* Loading overlay */ /*Loading overlay*/
.loading-overlay { .loading-overlay {
position: fixed; position: fixed;
top: 0; top: 0;
@@ -197,7 +198,7 @@ body {
z-index: 9999; z-index: 9999;
} }
/* Table enhancements */ /*Table enhancements*/
.table { .table {
border-color: #e0e5e9; border-color: #e0e5e9;
} }
@@ -224,7 +225,7 @@ body {
/* Consistent hover with sidebar */ /* Consistent hover with sidebar */
} }
/* Form improvements */ /*Form improvements*/
.form-control, .form-control,
.form-select { .form-select {
border-color: #ced4da; border-color: #ced4da;
@@ -246,7 +247,7 @@ body {
/* Bootstrap focus shadow */ /* Bootstrap focus shadow */
} }
/* Button styling */ /*Button styling*/
.btn { .btn {
border-radius: 0.375rem; border-radius: 0.375rem;
@@ -281,13 +282,13 @@ body {
border-color: #545b62; border-color: #545b62;
} }
/* Alert styling */ /*Alert styling*/
.alert { .alert {
border-radius: 0.375rem; border-radius: 0.375rem;
padding: 0.9rem 1.25rem; padding: 0.9rem 1.25rem;
} }
/* Chat transcript styling */ /*Chat transcript styling*/
.chat-transcript { .chat-transcript {
background-color: #f8f9fa; background-color: #f8f9fa;
border: 1px solid #e9ecef; border: 1px solid #e9ecef;
@@ -304,7 +305,7 @@ body {
margin-bottom: 0; margin-bottom: 0;
} }
/* Footer styling */ /*Footer styling*/
footer { footer {
background-color: #fff; background-color: #fff;
@@ -318,7 +319,7 @@ footer {
/* Added for sticky footer */ /* Added for sticky footer */
} }
/* Responsive adjustments */ /*Responsive adjustments*/
@media (width <=767.98px) { @media (width <=767.98px) {
.main-content { .main-content {
margin-left: 0; margin-left: 0;
@@ -337,7 +338,7 @@ footer {
} }
} }
/* Print styles */ /*Print styles*/
@media print { @media print {
.sidebar, .sidebar,
.navbar, .navbar,

View File

@@ -1,8 +1,9 @@
/** /**
* ajax-navigation.js - JavaScript for AJAX-based navigation across the entire application
* * ajax-navigation.js - JavaScript for AJAX-based navigation across the entire application
* This script handles AJAX navigation between pages in the Chat Analytics Dashboard. *
* It intercepts link clicks, loads content via AJAX, and updates the browser history. * This script handles AJAX navigation between pages in the Chat Analytics Dashboard.
* It intercepts link clicks, loads content via AJAX, and updates the browser history.
*/ */
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,8 +1,9 @@
/** /**
* ajax-pagination.js - Common JavaScript for AJAX pagination across the application
* * ajax-pagination.js - Common JavaScript for AJAX pagination across the application
* This script handles AJAX-based pagination for all pages in the Chat Analytics Dashboard. *
* It intercepts pagination link clicks, loads content via AJAX, and updates the browser history. * This script handles AJAX-based pagination for all pages in the Chat Analytics Dashboard.
* It intercepts pagination link clicks, loads content via AJAX, and updates the browser history.
*/ */
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,9 +1,10 @@
/** /**
* dashboard.js - JavaScript for the dashboard functionality
* * dashboard.js - JavaScript for the dashboard functionality
* This file handles the interactive features of the dashboard, *
* including chart refreshing, dashboard filtering, and dashboard * This file handles the interactive features of the dashboard,
* customization. * including chart refreshing, dashboard filtering, and dashboard
* customization.
*/ */
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View File

@@ -1,8 +1,9 @@
/** /**
* main.js - Global JavaScript functionality
* * main.js - Global JavaScript functionality
* This file contains general JavaScript functionality used across *
* the entire application, including navigation, forms, and UI interactions. * This file contains general JavaScript functionality used across
* the entire application, including navigation, forms, and UI interactions.
*/ */
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {

View File

@@ -4,7 +4,7 @@ WSGI config for dashboard_project project.
It exposes the WSGI callable as a module-level variable named ``application``. It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see For more information on this file, see
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ <https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/>
""" """
import os import os

16
dev.sh
View File

@@ -1,10 +1,13 @@
#!/bin/bash #!/bin/bash
# LiveGraphsDjango Development Helper Script # LiveGraphsDjango Development Helper Script
# Set UV_LINK_MODE to copy to avoid hardlink warnings # Set UV_LINK_MODE to copy to avoid hardlink warnings
export UV_LINK_MODE=copy export UV_LINK_MODE=copy
# Function to print section header # Function to print section header
print_header() { print_header() {
echo "======================================" echo "======================================"
echo "🚀 $1" echo "🚀 $1"
@@ -12,6 +15,7 @@ print_header() {
} }
# Display help menu # Display help menu
if [[ $1 == "help" ]] || [[ $1 == "-h" ]] || [[ $1 == "--help" ]] || [[ -z $1 ]]; then if [[ $1 == "help" ]] || [[ $1 == "-h" ]] || [[ $1 == "--help" ]] || [[ -z $1 ]]; then
print_header "LiveGraphsDjango Development Commands" print_header "LiveGraphsDjango Development Commands"
echo "Usage: ./dev.sh COMMAND" echo "Usage: ./dev.sh COMMAND"
@@ -33,6 +37,7 @@ if [[ $1 == "help" ]] || [[ $1 == "-h" ]] || [[ $1 == "--help" ]] || [[ -z $1 ]]
fi fi
# Start Redis server # Start Redis server
if [[ $1 == "redis-start" ]]; then if [[ $1 == "redis-start" ]]; then
print_header "Starting Redis Server" print_header "Starting Redis Server"
redis-server --daemonize yes redis-server --daemonize yes
@@ -46,6 +51,7 @@ if [[ $1 == "redis-start" ]]; then
fi fi
# Test Redis connection # Test Redis connection
if [[ $1 == "redis-test" ]]; then if [[ $1 == "redis-test" ]]; then
print_header "Testing Redis Connection" print_header "Testing Redis Connection"
cd dashboard_project && python manage.py test_redis cd dashboard_project && python manage.py test_redis
@@ -53,6 +59,7 @@ if [[ $1 == "redis-test" ]]; then
fi fi
# Stop Redis server # Stop Redis server
if [[ $1 == "redis-stop" ]]; then if [[ $1 == "redis-stop" ]]; then
print_header "Stopping Redis Server" print_header "Stopping Redis Server"
redis-cli shutdown redis-cli shutdown
@@ -61,6 +68,7 @@ if [[ $1 == "redis-stop" ]]; then
fi fi
# Run migrations # Run migrations
if [[ $1 == "migrate" ]]; then if [[ $1 == "migrate" ]]; then
print_header "Running Migrations" print_header "Running Migrations"
cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py migrate cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py migrate
@@ -68,6 +76,7 @@ if [[ $1 == "migrate" ]]; then
fi fi
# Make migrations # Make migrations
if [[ $1 == "makemigrations" ]]; then if [[ $1 == "makemigrations" ]]; then
print_header "Creating Migrations" print_header "Creating Migrations"
cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py makemigrations cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py makemigrations
@@ -75,6 +84,7 @@ if [[ $1 == "makemigrations" ]]; then
fi fi
# Create superuser # Create superuser
if [[ $1 == "superuser" ]]; then if [[ $1 == "superuser" ]]; then
print_header "Creating Superuser" print_header "Creating Superuser"
cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py createsuperuser cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py createsuperuser
@@ -82,6 +92,7 @@ if [[ $1 == "superuser" ]]; then
fi fi
# Test Celery # Test Celery
if [[ $1 == "test-celery" ]]; then if [[ $1 == "test-celery" ]]; then
print_header "Testing Celery" print_header "Testing Celery"
cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py test_celery cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py test_celery
@@ -89,6 +100,7 @@ if [[ $1 == "test-celery" ]]; then
fi fi
# View Celery logs # View Celery logs
if [[ $1 == "logs-celery" ]]; then if [[ $1 == "logs-celery" ]]; then
print_header "Celery Worker Logs" print_header "Celery Worker Logs"
echo "Press Ctrl+C to exit" echo "Press Ctrl+C to exit"
@@ -97,6 +109,7 @@ if [[ $1 == "logs-celery" ]]; then
fi fi
# View Celery Beat logs # View Celery Beat logs
if [[ $1 == "logs-beat" ]]; then if [[ $1 == "logs-beat" ]]; then
print_header "Celery Beat Logs" print_header "Celery Beat Logs"
echo "Press Ctrl+C to exit" echo "Press Ctrl+C to exit"
@@ -105,6 +118,7 @@ if [[ $1 == "logs-beat" ]]; then
fi fi
# Django shell # Django shell
if [[ $1 == "shell" ]]; then if [[ $1 == "shell" ]]; then
print_header "Django Shell" print_header "Django Shell"
cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py shell cd dashboard_project && UV_LINK_MODE=copy uv run python manage.py shell
@@ -112,6 +126,7 @@ if [[ $1 == "shell" ]]; then
fi fi
# Start the application # Start the application
if [[ $1 == "start" ]]; then if [[ $1 == "start" ]]; then
print_header "Starting LiveGraphsDjango Application" print_header "Starting LiveGraphsDjango Application"
./start.sh ./start.sh
@@ -119,6 +134,7 @@ if [[ $1 == "start" ]]; then
fi fi
# Invalid command # Invalid command
echo "❌ Unknown command: $1" echo "❌ Unknown command: $1"
echo "Run './dev.sh help' to see available commands" echo "Run './dev.sh help' to see available commands"
exit 1 exit 1

View File

@@ -1,35 +1,26 @@
{ {
"devDependencies": {
"markdownlint-cli2": "^0.18.1",
"prettier": "^3.5.3",
"prettier-plugin-jinja-template": "^2.1.0"
},
"scripts": { "scripts": {
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"lint:md": "markdownlint-cli2 \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"", "lint:md": "markdownlint-cli2 \"**/*.md\"",
"lint:md:fix": "markdownlint-cli2 --fix \"**/*.md\" \"!.trunk/**\" \"!.venv/**\" \"!node_modules/**\"" "lint:md:fix": "bun lint:md -- --fix"
},
"devDependencies": {
"markdownlint-cli2": "^0.18.1",
"prettier": "^3.6.2",
"prettier-plugin-jinja-template": "^2.1.0",
"prettier-plugin-packagejson": "^2.5.19"
}, },
"markdownlint-cli2": { "markdownlint-cli2": {
"config": { "config": {
"MD007": {
"indent": 4,
"start_indented": false,
"start_indent": 4
},
"MD013": false, "MD013": false,
"MD030": {
"ul_single": 3,
"ol_single": 2,
"ul_multi": 3,
"ol_multi": 2
},
"MD033": false "MD033": false
}, },
"ignores": [ "ignores": [
"node_modules",
".git", ".git",
"*.json" ".trunk",
".venv",
"node_modules"
] ]
} }
} }

View File

@@ -4,18 +4,16 @@ version = "0.1.0"
description = "Live Graphs Django Dashboard" description = "Live Graphs Django Dashboard"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
authors = [{ name = "LiveGraphs Team" }]
license = { text = "MIT" } license = { text = "MIT" }
authors = [{ name = "LiveGraphs Team" }]
classifiers = [ classifiers = [
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
"Framework :: Django", "Framework :: Django",
"Framework :: Django :: 5.2", "Framework :: Django :: 5.2",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.13",
] ]
dependencies = [ dependencies = [
"bleach[css]>=6.2.0", "bleach[css]>=6.2.0",
"celery[sqlalchemy]>=5.5.2", "celery[sqlalchemy]>=5.5.2",
@@ -37,6 +35,11 @@ dependencies = [
"xlsxwriter>=3.2.3", "xlsxwriter>=3.2.3",
] ]
[project.urls]
"Bug Tracker" = "https://github.com/kjanat/livegraphsdjango/issues"
"Documentation" = "https://github.com/kjanat/livegraphsdjango#readme"
"Source" = "https://github.com/kjanat/livegraphsdjango"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"bandit>=1.8.3", "bandit>=1.8.3",
@@ -55,14 +58,64 @@ dev = [
requires = ["setuptools>=69.0.0", "wheel>=0.42.0"] requires = ["setuptools>=69.0.0", "wheel>=0.42.0"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.setuptools] [tool.bandit]
packages = ["dashboard_project"] exclude_dirs = [
"tests",
"venv",
".venv",
".git",
"__pycache__",
"migrations",
"**/create_sample_data.py",
]
skips = ["B101"]
targets = ["dashboard_project"]
[tool.setuptools.package-data] [tool.coverage.run]
"dashboard_project" = ["static/**/*", "templates/**/*", "media/**/*"] 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",
]
[tool.django-stubs]
django_settings_module = "dashboard_project.settings"
[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.pytest.ini_options]
filterwarnings = [
"ignore::DeprecationWarning",
"ignore::PendingDeprecationWarning",
]
python_files = "test_*.py"
testpaths = ["dashboard_project"]
DJANGO_SETTINGS_MODULE = "dashboard_project.settings"
[tool.ruff] [tool.ruff]
# Exclude a variety of commonly ignored directories. preview = true
# Exclude a variety of commonly ignored directories
exclude = [ exclude = [
".bzr", ".bzr",
".direnv", ".direnv",
@@ -91,11 +144,9 @@ exclude = [
"site-packages", "site-packages",
"venv", "venv",
] ]
# Same as Black
# Same as Black.
line-length = 120 line-length = 120
indent-width = 4 indent-width = 4
# Assume Python 3.13 # Assume Python 3.13
target-version = "py313" target-version = "py313"
@@ -110,62 +161,8 @@ quote-style = "double"
indent-style = "space" indent-style = "space"
line-ending = "lf" line-ending = "lf"
[tool.bandit] [tool.setuptools]
exclude_dirs = [ packages = ["dashboard_project"]
"tests",
"venv",
".venv",
".git",
"__pycache__",
"migrations",
"**/create_sample_data.py",
]
skips = ["B101"]
targets = ["dashboard_project"]
[tool.mypy] [tool.setuptools.package-data]
python_version = "3.13" "dashboard_project" = ["static/__/*", "templates/__/*", "media/**/*"]
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"

View File

@@ -1,14 +1,18 @@
#!/bin/bash #!/bin/bash
# Set UV_LINK_MODE to copy to avoid hardlink warnings # Set UV_LINK_MODE to copy to avoid hardlink warnings
export UV_LINK_MODE=copy export UV_LINK_MODE=copy
# Check if Redis is running # Check if Redis is running
if ! redis-cli ping >/dev/null 2>&1; then if ! redis-cli ping >/dev/null 2>&1; then
echo "Starting Redis server..." echo "Starting Redis server..."
redis-server --daemonize yes redis-server --daemonize yes
sleep 1 sleep 1
# Verify Redis is now running # Verify Redis is now running
if redis-cli ping >/dev/null 2>&1; then if redis-cli ping >/dev/null 2>&1; then
echo "✅ Redis server is now running" echo "✅ Redis server is now running"
else else
@@ -22,6 +26,7 @@ else
fi fi
# Set environment variables for Redis if it's running # Set environment variables for Redis if it's running
if redis-cli ping >/dev/null 2>&1; then if redis-cli ping >/dev/null 2>&1; then
export CELERY_BROKER_URL=redis://localhost:6379/0 export CELERY_BROKER_URL=redis://localhost:6379/0
export CELERY_RESULT_BACKEND=redis://localhost:6379/0 export CELERY_RESULT_BACKEND=redis://localhost:6379/0
@@ -33,4 +38,5 @@ else
fi fi
# Start the application using foreman # Start the application using foreman
foreman start foreman start