django-cfg 1.3.13__py3-none-any.whl → 1.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/accounts/admin/user_admin.py +39 -16
- django_cfg/apps/accounts/serializers/profile.py +1 -1
- django_cfg/apps/accounts/services/otp_service.py +18 -11
- django_cfg/apps/accounts/signals.py +15 -24
- django_cfg/apps/accounts/utils/notifications.py +217 -358
- django_cfg/apps/accounts/views/otp.py +2 -2
- django_cfg/apps/accounts/views/webhook.py +1 -1
- django_cfg/apps/agents/core/django_agent.py +1 -1
- django_cfg/apps/agents/examples/__init__.py +3 -0
- django_cfg/apps/agents/examples/simple_example.py +161 -0
- django_cfg/apps/api/commands/views.py +66 -83
- django_cfg/apps/api/health/drf_views.py +269 -0
- django_cfg/apps/api/health/serializers.py +45 -0
- django_cfg/apps/api/health/urls.py +6 -1
- django_cfg/apps/knowbase/admin/actions/__init__.py +13 -0
- django_cfg/apps/knowbase/admin/actions/visibility_actions.py +56 -0
- django_cfg/apps/knowbase/admin/document_admin.py +136 -270
- django_cfg/apps/knowbase/admin/helpers/__init__.py +17 -0
- django_cfg/apps/knowbase/admin/helpers/configs.py +72 -0
- django_cfg/apps/knowbase/admin/helpers/display_helpers.py +156 -0
- django_cfg/apps/knowbase/admin/helpers/statistics.py +108 -0
- django_cfg/apps/knowbase/config/constance_fields.py +1 -1
- django_cfg/apps/knowbase/config/settings.py +2 -2
- django_cfg/apps/knowbase/examples/__init__.py +3 -0
- django_cfg/apps/knowbase/examples/external_data_usage.py +191 -0
- django_cfg/apps/knowbase/mixins/__init__.py +19 -2
- django_cfg/apps/knowbase/mixins/config/__init__.py +14 -0
- django_cfg/apps/knowbase/mixins/config/defaults.py +75 -0
- django_cfg/apps/knowbase/mixins/config/meta_config.py +120 -0
- django_cfg/apps/knowbase/mixins/creator.py +10 -10
- django_cfg/apps/knowbase/mixins/examples/vehicle_model_example.py +199 -0
- django_cfg/apps/knowbase/mixins/external_data_mixin.py +105 -403
- django_cfg/apps/knowbase/mixins/generators/__init__.py +16 -0
- django_cfg/apps/knowbase/mixins/generators/content_generator.py +218 -0
- django_cfg/apps/knowbase/mixins/generators/field_analyzer.py +76 -0
- django_cfg/apps/knowbase/mixins/generators/metadata_generator.py +124 -0
- django_cfg/apps/knowbase/mixins/service.py +2 -2
- django_cfg/apps/knowbase/services/archive/__init__.py +1 -0
- django_cfg/apps/knowbase/services/archive/analyzers/__init__.py +17 -0
- django_cfg/apps/knowbase/services/archive/analyzers/complexity_analyzer.py +33 -0
- django_cfg/apps/knowbase/services/archive/analyzers/purpose_detector.py +36 -0
- django_cfg/apps/knowbase/services/archive/analyzers/quality_analyzer.py +39 -0
- django_cfg/apps/knowbase/services/archive/analyzers/tag_generator.py +103 -0
- django_cfg/apps/knowbase/services/archive/chunking/__init__.py +19 -0
- django_cfg/apps/knowbase/services/archive/chunking/base.py +81 -0
- django_cfg/apps/knowbase/services/archive/chunking/json_chunker.py +62 -0
- django_cfg/apps/knowbase/services/archive/chunking/markdown_chunker.py +107 -0
- django_cfg/apps/knowbase/services/archive/chunking/python_chunker.py +248 -0
- django_cfg/apps/knowbase/services/archive/chunking/text_chunker.py +70 -0
- django_cfg/apps/knowbase/services/archive/chunking_service.py +110 -729
- django_cfg/apps/knowbase/services/archive/context/__init__.py +14 -0
- django_cfg/apps/knowbase/services/archive/context/builders.py +220 -0
- django_cfg/apps/knowbase/services/archive/context/models.py +38 -0
- django_cfg/apps/knowbase/services/embedding/models.py +18 -14
- django_cfg/apps/knowbase/services/embedding/processors.py +6 -3
- django_cfg/apps/knowbase/tasks/document_processing.py +11 -3
- django_cfg/apps/leads/tests.py +1 -1
- django_cfg/apps/payments/admin/api_keys_admin.py +1 -1
- django_cfg/apps/payments/admin/balance_admin.py +1 -1
- django_cfg/apps/payments/admin/currencies_admin.py +1 -1
- django_cfg/apps/payments/admin/payments_admin.py +1 -1
- django_cfg/apps/payments/admin/subscriptions_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/templates/payments/base.html +59 -126
- django_cfg/apps/payments/admin_interface/views/api/payments.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/stats.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/users.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_admin.py +1 -1
- django_cfg/apps/payments/admin_interface/views/api/webhook_public.py +1 -1
- django_cfg/apps/payments/admin_interface/views/base.py +29 -2
- django_cfg/apps/payments/apps.py +1 -1
- django_cfg/apps/payments/config/django_cfg_integration.py +2 -2
- django_cfg/apps/payments/config/helpers.py +3 -2
- django_cfg/apps/payments/management/commands/cleanup_expired_data.py +1 -1
- django_cfg/apps/payments/management/commands/currency_stats.py +1 -1
- django_cfg/apps/payments/management/commands/manage_currencies.py +1 -1
- django_cfg/apps/payments/management/commands/manage_providers.py +1 -1
- django_cfg/apps/payments/management/commands/process_pending_payments.py +1 -1
- django_cfg/apps/payments/management/commands/test_providers.py +1 -1
- django_cfg/apps/payments/middleware/api_access.py +1 -1
- django_cfg/apps/payments/middleware/rate_limiting.py +1 -1
- django_cfg/apps/payments/middleware/usage_tracking.py +1 -1
- django_cfg/apps/payments/models/balance.py +2 -2
- django_cfg/apps/payments/models/managers/api_key_managers.py +1 -1
- django_cfg/apps/payments/models/managers/balance_managers.py +1 -1
- django_cfg/apps/payments/models/managers/currency_managers.py +1 -1
- django_cfg/apps/payments/models/managers/payment_managers.py +1 -1
- django_cfg/apps/payments/models/managers/subscription_managers.py +1 -1
- django_cfg/apps/payments/models/payments.py +2 -2
- django_cfg/apps/payments/services/cache_service/__init__.py +1 -1
- django_cfg/apps/payments/services/cache_service/simple_cache.py +10 -5
- django_cfg/apps/payments/services/core/base.py +1 -1
- django_cfg/apps/payments/services/core/currency/__init__.py +13 -0
- django_cfg/apps/payments/services/core/currency/currency_converter.py +57 -0
- django_cfg/apps/payments/services/core/currency/currency_validator.py +61 -0
- django_cfg/apps/payments/services/core/operations/__init__.py +15 -0
- django_cfg/apps/payments/services/core/operations/payment_canceller.py +100 -0
- django_cfg/apps/payments/services/core/operations/payment_creator.py +196 -0
- django_cfg/apps/payments/services/core/operations/status_checker.py +100 -0
- django_cfg/apps/payments/services/core/payment_service.py +124 -612
- django_cfg/apps/payments/services/core/providers/__init__.py +13 -0
- django_cfg/apps/payments/services/core/providers/provider_client.py +132 -0
- django_cfg/apps/payments/services/core/providers/status_mapper.py +89 -0
- django_cfg/apps/payments/services/core/utils/__init__.py +13 -0
- django_cfg/apps/payments/services/core/utils/data_converter.py +48 -0
- django_cfg/apps/payments/services/core/utils/statistics_calculator.py +69 -0
- django_cfg/apps/payments/services/providers/base.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/__init__.py +3 -3
- django_cfg/apps/payments/services/providers/nowpayments/parsers/__init__.py +9 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/__init__.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/constants.py +23 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/currency_names.py +244 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/data/patterns.py +511 -0
- django_cfg/apps/payments/services/providers/nowpayments/parsers/parser.py +168 -0
- django_cfg/apps/payments/services/providers/nowpayments/provider.py +1 -1
- django_cfg/apps/payments/services/providers/nowpayments/sync.py +1 -1
- django_cfg/apps/payments/services/providers/registry.py +1 -1
- django_cfg/apps/payments/services/providers/sync_service.py +1 -1
- django_cfg/apps/payments/signals/__init__.py +1 -1
- django_cfg/apps/payments/signals/api_key_signals.py +1 -1
- django_cfg/apps/payments/signals/balance_signals.py +1 -1
- django_cfg/apps/payments/signals/payment_signals.py +1 -1
- django_cfg/apps/payments/signals/subscription_signals.py +1 -1
- django_cfg/apps/payments/views/api/api_keys.py +1 -1
- django_cfg/apps/payments/views/api/balances.py +1 -1
- django_cfg/apps/payments/views/api/base.py +1 -1
- django_cfg/apps/payments/views/api/currencies.py +1 -1
- django_cfg/apps/payments/views/api/payments.py +1 -1
- django_cfg/apps/payments/views/api/subscriptions.py +1 -1
- django_cfg/apps/payments/views/api/webhooks.py +1 -1
- django_cfg/apps/payments/views/serializers/api_keys.py +1 -1
- django_cfg/apps/payments/views/serializers/balances.py +1 -1
- django_cfg/apps/payments/views/serializers/currencies.py +1 -1
- django_cfg/apps/payments/views/serializers/payments.py +1 -1
- django_cfg/apps/payments/views/serializers/subscriptions.py +1 -1
- django_cfg/apps/payments/views/serializers/webhooks.py +1 -1
- django_cfg/apps/support/admin/support_admin.py +21 -13
- django_cfg/apps/support/templates/support/chat/access_denied.html +21 -27
- django_cfg/apps/support/templates/support/chat/ticket_chat.html +183 -254
- django_cfg/apps/support/utils/support_email_service.py +1 -1
- django_cfg/apps/tasks/templates/tasks/layout/base.html +20 -115
- django_cfg/apps/tasks/utils/simulator.py +1 -1
- django_cfg/apps/tasks/views/dashboard.py +33 -3
- django_cfg/apps/urls.py +5 -1
- django_cfg/cli/README.md +57 -471
- django_cfg/cli/commands/create_project.py +140 -529
- django_cfg/cli/main.py +13 -10
- django_cfg/core/__init__.py +63 -6
- django_cfg/core/base/__init__.py +5 -0
- django_cfg/core/base/config_model.py +652 -0
- django_cfg/core/builders/__init__.py +11 -0
- django_cfg/core/builders/apps_builder.py +258 -0
- django_cfg/core/builders/middleware_builder.py +115 -0
- django_cfg/core/builders/security_builder.py +96 -0
- django_cfg/core/config.py +20 -892
- django_cfg/core/constants.py +69 -0
- django_cfg/core/environment/__init__.py +9 -0
- django_cfg/core/exceptions.py +45 -298
- django_cfg/core/generation/__init__.py +51 -0
- django_cfg/core/generation/core_generators/__init__.py +0 -0
- django_cfg/core/generation/core_generators/settings.py +90 -0
- django_cfg/core/generation/core_generators/static.py +82 -0
- django_cfg/core/generation/core_generators/templates.py +141 -0
- django_cfg/core/generation/data_generators/__init__.py +15 -0
- django_cfg/core/generation/data_generators/cache.py +132 -0
- django_cfg/core/generation/data_generators/database.py +117 -0
- django_cfg/core/generation/generation.py +92 -0
- django_cfg/core/generation/integration_generators/__init__.py +21 -0
- django_cfg/core/generation/integration_generators/api.py +237 -0
- django_cfg/core/generation/integration_generators/sessions.py +65 -0
- django_cfg/core/generation/integration_generators/tailwind.py +54 -0
- django_cfg/core/generation/integration_generators/tasks.py +92 -0
- django_cfg/core/generation/integration_generators/third_party.py +144 -0
- django_cfg/core/generation/orchestrator.py +285 -0
- django_cfg/core/generation/protocols.py +30 -0
- django_cfg/core/generation/security_generators/__init__.py +0 -0
- django_cfg/core/generation/utility_generators/__init__.py +24 -0
- django_cfg/core/generation/utility_generators/email.py +58 -0
- django_cfg/core/generation/utility_generators/i18n.py +66 -0
- django_cfg/core/generation/utility_generators/limits.py +58 -0
- django_cfg/core/generation/utility_generators/logging.py +66 -0
- django_cfg/core/generation/utility_generators/security.py +101 -0
- django_cfg/core/generation/utils/__init__.py +0 -0
- django_cfg/core/generation/utils/helpers.py +32 -0
- django_cfg/core/integration/__init__.py +18 -25
- django_cfg/core/integration/display/startup.py +146 -133
- django_cfg/core/integration/url_integration.py +13 -2
- django_cfg/core/services/__init__.py +5 -0
- django_cfg/core/services/config_service.py +121 -0
- django_cfg/core/state/__init__.py +9 -0
- django_cfg/core/state/registry.py +84 -0
- django_cfg/core/types/__init__.py +15 -0
- django_cfg/core/types/aliases.py +15 -0
- django_cfg/core/types/enums.py +49 -0
- django_cfg/dashboard/DEBUG_README.md +105 -0
- django_cfg/dashboard/REFACTORING_SUMMARY.md +237 -0
- django_cfg/dashboard/__init__.py +24 -0
- django_cfg/dashboard/components.py +308 -0
- django_cfg/dashboard/debug.py +176 -0
- django_cfg/dashboard/management/__init__.py +0 -0
- django_cfg/dashboard/management/commands/__init__.py +0 -0
- django_cfg/dashboard/management/commands/debug_dashboard.py +109 -0
- django_cfg/dashboard/sections/__init__.py +1 -0
- django_cfg/dashboard/sections/base.py +128 -0
- django_cfg/dashboard/sections/commands.py +32 -0
- django_cfg/dashboard/sections/overview.py +394 -0
- django_cfg/dashboard/sections/stats.py +48 -0
- django_cfg/dashboard/sections/system.py +73 -0
- django_cfg/management/commands/check_settings.py +6 -2
- django_cfg/management/commands/clear_constance.py +6 -1
- django_cfg/management/commands/create_token.py +5 -4
- django_cfg/management/commands/generate.py +5 -0
- django_cfg/management/commands/list_urls.py +7 -2
- django_cfg/management/commands/migrate_all.py +6 -2
- django_cfg/management/commands/migrator.py +6 -1
- django_cfg/management/commands/rundramatiq.py +6 -1
- django_cfg/management/commands/rundramatiq_simulator.py +11 -4
- django_cfg/management/commands/runserver_ngrok.py +9 -7
- django_cfg/management/commands/script.py +25 -21
- django_cfg/management/commands/show_config.py +6 -1
- django_cfg/management/commands/show_urls.py +8 -3
- django_cfg/management/commands/superuser.py +5 -4
- django_cfg/management/commands/task_clear.py +8 -3
- django_cfg/management/commands/task_status.py +8 -3
- django_cfg/management/commands/test_email.py +6 -1
- django_cfg/management/commands/test_telegram.py +6 -1
- django_cfg/management/commands/test_twilio.py +6 -1
- django_cfg/management/commands/tree.py +7 -4
- django_cfg/models/__init__.py +88 -3
- django_cfg/models/api/__init__.py +27 -0
- django_cfg/models/{api.py → api/config.py} +1 -1
- django_cfg/models/api/drf/__init__.py +21 -0
- django_cfg/models/api/drf/config.py +101 -0
- django_cfg/models/api/drf/redoc.py +31 -0
- django_cfg/models/api/drf/spectacular.py +129 -0
- django_cfg/models/api/drf/swagger.py +59 -0
- django_cfg/models/{api_keys.py → api/keys.py} +16 -6
- django_cfg/models/{limits.py → api/limits.py} +0 -1
- django_cfg/models/base/__init__.py +14 -0
- django_cfg/models/django/__init__.py +16 -0
- django_cfg/models/{constance.py → django/constance.py} +1 -1
- django_cfg/models/{environment.py → django/environment.py} +1 -1
- django_cfg/models/infrastructure/__init__.py +17 -0
- django_cfg/models/{cache.py → infrastructure/cache.py} +3 -2
- django_cfg/models/infrastructure/database/__init__.py +22 -0
- django_cfg/models/infrastructure/database/config.py +265 -0
- django_cfg/models/infrastructure/database/converters.py +91 -0
- django_cfg/models/infrastructure/database/parsers.py +96 -0
- django_cfg/models/infrastructure/database/routing.py +85 -0
- django_cfg/models/infrastructure/database/validators.py +170 -0
- django_cfg/models/{logging.py → infrastructure/logging.py} +1 -1
- django_cfg/models/{security.py → infrastructure/security.py} +2 -2
- django_cfg/models/ngrok/__init__.py +11 -0
- django_cfg/models/ngrok/auth.py +37 -0
- django_cfg/models/ngrok/config.py +77 -0
- django_cfg/models/ngrok/tunnel.py +35 -0
- django_cfg/models/payments/__init__.py +20 -0
- django_cfg/models/payments/api_keys.py +57 -0
- django_cfg/models/{payments.py → payments/config.py} +56 -154
- django_cfg/models/payments/providers/__init__.py +15 -0
- django_cfg/models/payments/providers/base.py +25 -0
- django_cfg/models/payments/providers/nowpayments.py +48 -0
- django_cfg/models/services/__init__.py +18 -0
- django_cfg/models/services/base.py +65 -0
- django_cfg/models/{email.py → services/email.py} +1 -1
- django_cfg/models/services/telegram.py +172 -0
- django_cfg/models/tasks/__init__.py +51 -0
- django_cfg/models/tasks/backends.py +250 -0
- django_cfg/models/tasks/config.py +314 -0
- django_cfg/models/tasks/utils.py +174 -0
- django_cfg/modules/base.py +18 -3
- django_cfg/modules/django_admin/decorators/actions.py +1 -1
- django_cfg/modules/django_admin/decorators/display.py +1 -1
- django_cfg/modules/django_admin/mixins/standalone_actions_mixin.py +1 -1
- django_cfg/modules/django_currency/examples/__init__.py +3 -0
- django_cfg/modules/django_currency/examples/example_database_usage.py +144 -0
- django_cfg/modules/django_drf_theme/CHANGELOG.md +210 -0
- django_cfg/modules/django_drf_theme/EXAMPLE.md +465 -0
- django_cfg/modules/django_drf_theme/IMPLEMENTATION.md +232 -0
- django_cfg/modules/django_drf_theme/README.md +207 -0
- django_cfg/modules/django_drf_theme/TAILWIND_CDN_GUIDE.md +274 -0
- django_cfg/modules/django_drf_theme/__init__.py +23 -0
- django_cfg/modules/django_drf_theme/apps.py +15 -0
- django_cfg/modules/django_drf_theme/renderers.py +58 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/api.html +375 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/base.html +938 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/filter_form.html +132 -0
- django_cfg/modules/django_drf_theme/templates/rest_framework/tailwind/forms/raw_data_form.html +123 -0
- django_cfg/modules/django_drf_theme/templatetags/__init__.py +1 -0
- django_cfg/modules/django_drf_theme/templatetags/tailwind_tags.py +57 -0
- django_cfg/modules/django_email/__init__.py +14 -0
- django_cfg/modules/{django_email.py → django_email/service.py} +78 -113
- django_cfg/modules/django_email/utils.py +40 -0
- django_cfg/modules/django_health/__init__.py +9 -0
- django_cfg/modules/{django_health.py → django_health/service.py} +23 -21
- django_cfg/modules/django_ipc_client/README.md +346 -0
- django_cfg/modules/django_ipc_client/__init__.py +51 -0
- django_cfg/modules/django_ipc_client/client.py +540 -0
- django_cfg/modules/django_ipc_client/config.py +207 -0
- django_cfg/modules/django_ipc_client/dashboard/README.md +517 -0
- django_cfg/modules/django_ipc_client/dashboard/UNFOLD_INTEGRATION.md +439 -0
- django_cfg/modules/django_ipc_client/dashboard/__init__.py +11 -0
- django_cfg/modules/django_ipc_client/dashboard/apps.py +22 -0
- django_cfg/modules/django_ipc_client/dashboard/monitor.py +435 -0
- django_cfg/modules/django_ipc_client/dashboard/static/django_ipc_dashboard/js/dashboard.js +373 -0
- django_cfg/modules/django_ipc_client/dashboard/templates/django_ipc_dashboard/base.html +76 -0
- django_cfg/modules/django_ipc_client/dashboard/templates/django_ipc_dashboard/dashboard.html +200 -0
- django_cfg/modules/django_ipc_client/dashboard/urls.py +22 -0
- django_cfg/modules/django_ipc_client/dashboard/urls_admin.py +9 -0
- django_cfg/modules/django_ipc_client/dashboard/views.py +251 -0
- django_cfg/modules/django_ipc_client/exceptions.py +201 -0
- django_cfg/modules/django_llm/llm/client.py +155 -550
- django_cfg/modules/django_llm/llm/embeddings/__init__.py +13 -0
- django_cfg/modules/django_llm/llm/embeddings/mock_embedder.py +106 -0
- django_cfg/modules/django_llm/llm/embeddings/openai_embedder.py +79 -0
- django_cfg/modules/django_llm/llm/models_api/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/models_api/models_query.py +163 -0
- django_cfg/modules/django_llm/llm/providers/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/providers/config_builder.py +103 -0
- django_cfg/modules/django_llm/llm/providers/provider_manager.py +148 -0
- django_cfg/modules/django_llm/llm/providers/provider_selector.py +60 -0
- django_cfg/modules/django_llm/llm/requests/__init__.py +15 -0
- django_cfg/modules/django_llm/llm/requests/cache_manager.py +170 -0
- django_cfg/modules/django_llm/llm/requests/chat_handler.py +199 -0
- django_cfg/modules/django_llm/llm/requests/embedding_handler.py +113 -0
- django_cfg/modules/django_llm/llm/responses/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/responses/response_builder.py +131 -0
- django_cfg/modules/django_llm/llm/stats/__init__.py +9 -0
- django_cfg/modules/django_llm/llm/stats/stats_manager.py +107 -0
- django_cfg/modules/django_llm/translator/detectors/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/detectors/language_detector.py +90 -0
- django_cfg/modules/django_llm/translator/detectors/script_detector.py +153 -0
- django_cfg/modules/django_llm/translator/stats/__init__.py +11 -0
- django_cfg/modules/django_llm/translator/stats/stats_tracker.py +85 -0
- django_cfg/modules/django_llm/translator/translator.py +150 -603
- django_cfg/modules/django_llm/translator/translators/__init__.py +15 -0
- django_cfg/modules/django_llm/translator/translators/json_translator.py +316 -0
- django_cfg/modules/django_llm/translator/translators/text_translator.py +139 -0
- django_cfg/modules/django_llm/translator/utils/__init__.py +13 -0
- django_cfg/modules/django_llm/translator/utils/prompt_builder.py +110 -0
- django_cfg/modules/django_llm/translator/utils/text_utils.py +114 -0
- django_cfg/modules/django_logging/FIXES_SUMMARY.md +276 -0
- django_cfg/modules/django_logging/LOGGING_GUIDE.md +504 -0
- django_cfg/modules/django_logging/__init__.py +14 -0
- django_cfg/modules/{django_logger.py → django_logging/django_logger.py} +13 -13
- django_cfg/modules/{logger.py → django_logging/logger.py} +14 -4
- django_cfg/modules/django_ngrok/__init__.py +39 -0
- django_cfg/modules/{django_ngrok.py → django_ngrok/service.py} +14 -42
- django_cfg/modules/django_rpc_old/POETRY.md +344 -0
- django_cfg/modules/django_rpc_old/README.md +397 -0
- django_cfg/modules/django_rpc_old/TESTING.md +358 -0
- django_cfg/modules/django_rpc_old/__init__.py +39 -0
- django_cfg/modules/django_rpc_old/client.py +531 -0
- django_cfg/modules/django_rpc_old/config.py +279 -0
- django_cfg/modules/django_rpc_old/exceptions.py +172 -0
- django_cfg/modules/django_tailwind/README.md +478 -0
- django_cfg/modules/django_tailwind/__init__.py +7 -0
- django_cfg/modules/django_tailwind/apps.py +10 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/app.html +5 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/base.html +117 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +124 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/theme_toggle.html +54 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/user_menu.html +116 -0
- django_cfg/modules/django_tailwind/templates/django_tailwind/simple.html +46 -0
- django_cfg/modules/django_tailwind/templatetags/__init__.py +1 -0
- django_cfg/modules/django_tailwind/templatetags/tailwind_info.py +185 -0
- django_cfg/modules/django_tasks/__init__.py +29 -0
- django_cfg/modules/django_tasks/factory.py +127 -0
- django_cfg/modules/{django_tasks.py → django_tasks/service.py} +45 -274
- django_cfg/modules/django_tasks/settings.py +107 -0
- django_cfg/modules/django_telegram/__init__.py +29 -0
- django_cfg/modules/{django_telegram.py → django_telegram/service.py} +45 -113
- django_cfg/modules/django_telegram/utils.py +62 -0
- django_cfg/modules/django_twilio/__init__.py +54 -107
- django_cfg/modules/django_twilio/_imports.py +30 -0
- django_cfg/modules/django_twilio/base.py +192 -0
- django_cfg/modules/django_twilio/email_otp.py +227 -0
- django_cfg/modules/django_twilio/sendgrid_service.py +1 -1
- django_cfg/modules/django_twilio/simple_service.py +1 -2
- django_cfg/modules/django_twilio/sms.py +94 -0
- django_cfg/modules/django_twilio/twilio_service.py +2 -3
- django_cfg/modules/django_twilio/unified.py +310 -0
- django_cfg/modules/django_twilio/utils.py +190 -0
- django_cfg/modules/django_twilio/whatsapp.py +137 -0
- django_cfg/modules/django_unfold/callbacks/base.py +198 -7
- django_cfg/modules/django_unfold/callbacks/main.py +102 -10
- django_cfg/modules/django_unfold/dashboard.py +65 -43
- django_cfg/modules/django_unfold/models/config.py +13 -12
- django_cfg/modules/django_unfold/models/navigation.py +8 -3
- django_cfg/modules/django_unfold/models/tabs.py +2 -2
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +102 -0
- django_cfg/registry/core.py +24 -26
- django_cfg/registry/modules.py +5 -2
- django_cfg/registry/services.py +20 -3
- django_cfg/registry/third_party.py +8 -8
- django_cfg/static/admin/css/dashboard.css +260 -0
- django_cfg/static/admin/js/commands.js +171 -0
- django_cfg/static/admin/js/dashboard.js +126 -0
- django_cfg/templates/admin/components/management_commands.js +375 -0
- django_cfg/templates/admin/components/progress_bar.html +18 -23
- django_cfg/templates/admin/examples/component_class_example.html +156 -0
- django_cfg/templates/admin/index.html +48 -20
- django_cfg/templates/admin/index_new.html +106 -0
- django_cfg/templates/admin/layouts/base_dashboard.html +60 -0
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +1 -20
- django_cfg/templates/admin/sections/commands_section.html +626 -0
- django_cfg/templates/admin/sections/overview_section.html +112 -0
- django_cfg/templates/admin/sections/stats_section.html +35 -0
- django_cfg/templates/admin/sections/system_section.html +99 -0
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +322 -0
- django_cfg/templates/admin/snippets/components/activity_tracker.html +85 -47
- django_cfg/templates/admin/snippets/components/charts_section.html +154 -64
- django_cfg/templates/admin/snippets/components/django_commands.html +3 -3
- django_cfg/templates/admin/snippets/components/recent_activity_improved.html +25 -0
- django_cfg/templates/admin/snippets/components/recent_users_table.html +1 -1
- django_cfg/templates/admin/snippets/components/system_metrics.html +179 -93
- django_cfg/templates/admin/snippets/zones/zones_table.html +2 -2
- django_cfg/templatetags/django_cfg.py +7 -1
- django_cfg/utils/smart_defaults.py +4 -4
- django_cfg-1.4.3.dist-info/METADATA +533 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/RECORD +432 -195
- django_cfg/apps/accounts/utils/auth_email_service.py +0 -84
- django_cfg/apps/payments/services/providers/nowpayments/parsers.py +0 -879
- django_cfg/core/generation.py +0 -621
- django_cfg/management/commands/validate_config.py +0 -189
- django_cfg/models/database.py +0 -480
- django_cfg/models/drf.py +0 -272
- django_cfg/models/ngrok.py +0 -122
- django_cfg/models/services.py +0 -440
- django_cfg/models/tasks.py +0 -550
- django_cfg/modules/django_twilio/service.py +0 -942
- django_cfg/template_archive/django_sample.zip +0 -0
- django_cfg/templates/rest_framework/api.html +0 -12
- django_cfg/utils/toolkit.py +0 -703
- django_cfg-1.3.13.dist-info/METADATA +0 -1029
- /django_cfg/apps/accounts/management/commands/{test_otp.py → otp_test.py} +0 -0
- /django_cfg/core/{environment.py → environment/detector.py} +0 -0
- /django_cfg/models/{cors.py → api/cors.py} +0 -0
- /django_cfg/models/{jwt.py → api/jwt.py} +0 -0
- /django_cfg/models/{base.py → base/config.py} +0 -0
- /django_cfg/models/{cfg.py → base/module.py} +0 -0
- /django_cfg/models/{revolution.py → django/revolution.py} +0 -0
- /django_cfg/modules/{dramatiq_setup.py → django_tasks/dramatiq_setup.py} +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/WHEEL +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.3.13.dist-info → django_cfg-1.4.3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,938 @@
|
|
1
|
+
{% load static rest_framework tailwind_tags %}
|
2
|
+
<!DOCTYPE html>
|
3
|
+
<html lang="en" class="{{ html_class }}" x-data="drfApp()" x-init="init()" @keydown.window="handleKeyboard($event)">
|
4
|
+
<head>
|
5
|
+
<meta charset="utf-8">
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7
|
+
<meta name="robots" content="NONE,NOARCHIVE">
|
8
|
+
|
9
|
+
<title>{% if name %}{{ name }} – {% endif %}{{ project_name|default:"API" }}</title>
|
10
|
+
|
11
|
+
{# CSRF Token for AJAX requests #}
|
12
|
+
{% csrf_token %}
|
13
|
+
<meta name="csrf-token" content="{{ csrf_token }}">
|
14
|
+
|
15
|
+
{# DRF CSRF data for ajax-form.js #}
|
16
|
+
<script id="drf_csrf" type="application/json">
|
17
|
+
{
|
18
|
+
"csrfToken": "{{ csrf_token }}",
|
19
|
+
"csrfHeaderName": "X-CSRFToken"
|
20
|
+
}
|
21
|
+
</script>
|
22
|
+
|
23
|
+
{# Apply theme immediately to prevent flash #}
|
24
|
+
<script>
|
25
|
+
(function() {
|
26
|
+
const theme = '{{ theme|default:"auto" }}';
|
27
|
+
const html = document.documentElement;
|
28
|
+
let isDark = false;
|
29
|
+
|
30
|
+
if (theme === 'dark') {
|
31
|
+
html.classList.add('dark');
|
32
|
+
isDark = true;
|
33
|
+
} else if (theme === 'light') {
|
34
|
+
html.classList.remove('dark');
|
35
|
+
isDark = false;
|
36
|
+
} else {
|
37
|
+
// Auto: use system preference
|
38
|
+
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
39
|
+
if (isDark) {
|
40
|
+
html.classList.add('dark');
|
41
|
+
} else {
|
42
|
+
html.classList.remove('dark');
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
// Store for Prism.js theme switching
|
47
|
+
window.__drfThemeIsDark = isDark;
|
48
|
+
})();
|
49
|
+
</script>
|
50
|
+
|
51
|
+
{# Tailwind CSS #}
|
52
|
+
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,container-queries"></script>
|
53
|
+
|
54
|
+
{# Override Tailwind CDN's media-query dark mode with class-based dark mode #}
|
55
|
+
<style>
|
56
|
+
/* Force light mode colors when NOT in dark mode - override media queries */
|
57
|
+
html:not(.dark) body,
|
58
|
+
html:not(.dark) .dark\:bg-gray-900,
|
59
|
+
html:not(.dark) .dark\:bg-gray-800,
|
60
|
+
html:not(.dark) .dark\:bg-black {
|
61
|
+
background-color: rgb(249 250 251) !important; /* bg-gray-50 */
|
62
|
+
}
|
63
|
+
|
64
|
+
html:not(.dark) .dark\:bg-gray-700 {
|
65
|
+
background-color: rgb(243 244 246) !important; /* bg-gray-100 */
|
66
|
+
}
|
67
|
+
|
68
|
+
html:not(.dark) .dark\:text-gray-100,
|
69
|
+
html:not(.dark) .dark\:text-white,
|
70
|
+
html:not(.dark) .dark\:text-gray-300 {
|
71
|
+
color: rgb(17 24 39) !important; /* text-gray-900 */
|
72
|
+
}
|
73
|
+
|
74
|
+
html:not(.dark) .dark\:text-gray-400 {
|
75
|
+
color: rgb(75 85 99) !important; /* text-gray-600 */
|
76
|
+
}
|
77
|
+
|
78
|
+
html:not(.dark) .dark\:border-gray-700,
|
79
|
+
html:not(.dark) .dark\:border-gray-600 {
|
80
|
+
border-color: rgb(229 231 235) !important; /* border-gray-200 */
|
81
|
+
}
|
82
|
+
|
83
|
+
/* Enforce dark mode colors when IN dark mode */
|
84
|
+
html.dark body,
|
85
|
+
html.dark .dark\:bg-gray-900 {
|
86
|
+
background-color: rgb(17 24 39) !important;
|
87
|
+
}
|
88
|
+
|
89
|
+
html.dark .dark\:bg-gray-800 {
|
90
|
+
background-color: rgb(31 41 55) !important;
|
91
|
+
}
|
92
|
+
|
93
|
+
html.dark .dark\:bg-black {
|
94
|
+
background-color: rgb(0 0 0) !important;
|
95
|
+
}
|
96
|
+
|
97
|
+
html.dark .dark\:text-gray-100 {
|
98
|
+
color: rgb(243 244 246) !important;
|
99
|
+
}
|
100
|
+
|
101
|
+
html.dark .dark\:text-white {
|
102
|
+
color: rgb(255 255 255) !important;
|
103
|
+
}
|
104
|
+
</style>
|
105
|
+
|
106
|
+
{# Prism.js for syntax highlighting - loaded dynamically based on theme #}
|
107
|
+
<link rel="stylesheet" id="prism-light" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism.min.css" disabled>
|
108
|
+
<link rel="stylesheet" id="prism-dark" href="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/themes/prism-tomorrow.min.css" disabled>
|
109
|
+
<script>
|
110
|
+
// Enable the correct Prism theme immediately
|
111
|
+
if (window.__drfThemeIsDark) {
|
112
|
+
document.getElementById('prism-dark').disabled = false;
|
113
|
+
} else {
|
114
|
+
document.getElementById('prism-light').disabled = false;
|
115
|
+
}
|
116
|
+
</script>
|
117
|
+
|
118
|
+
{# Alpine.js for interactivity #}
|
119
|
+
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
120
|
+
|
121
|
+
{# Custom styles #}
|
122
|
+
<style>
|
123
|
+
/* Glass morphism */
|
124
|
+
.glass {
|
125
|
+
background: rgba(255, 255, 255, 0.9);
|
126
|
+
backdrop-filter: blur(10px);
|
127
|
+
-webkit-backdrop-filter: blur(10px);
|
128
|
+
}
|
129
|
+
.dark .glass {
|
130
|
+
background: rgba(17, 24, 39, 0.9);
|
131
|
+
}
|
132
|
+
|
133
|
+
/* Smooth transitions */
|
134
|
+
* {
|
135
|
+
transition-property: background-color, border-color, color, fill, stroke;
|
136
|
+
transition-duration: 200ms;
|
137
|
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
138
|
+
}
|
139
|
+
|
140
|
+
/* Custom scrollbar */
|
141
|
+
::-webkit-scrollbar {
|
142
|
+
width: 8px;
|
143
|
+
height: 8px;
|
144
|
+
}
|
145
|
+
::-webkit-scrollbar-track {
|
146
|
+
@apply bg-gray-100 dark:bg-gray-800;
|
147
|
+
}
|
148
|
+
::-webkit-scrollbar-thumb {
|
149
|
+
@apply bg-gray-300 dark:bg-gray-600 rounded-full;
|
150
|
+
}
|
151
|
+
::-webkit-scrollbar-thumb:hover {
|
152
|
+
@apply bg-gray-400 dark:bg-gray-500;
|
153
|
+
}
|
154
|
+
|
155
|
+
/* Shimmer animation for loading */
|
156
|
+
@keyframes shimmer {
|
157
|
+
0% { background-position: -1000px 0; }
|
158
|
+
100% { background-position: 1000px 0; }
|
159
|
+
}
|
160
|
+
.shimmer {
|
161
|
+
animation: shimmer 2s infinite;
|
162
|
+
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
|
163
|
+
background-size: 1000px 100%;
|
164
|
+
}
|
165
|
+
|
166
|
+
/* Pygments dark theme overrides */
|
167
|
+
.dark .highlight { background: #1f2937 !important; }
|
168
|
+
.dark .highlight .c { color: #6ee7b7; } /* Comment */
|
169
|
+
.dark .highlight .k { color: #c084fc; } /* Keyword */
|
170
|
+
.dark .highlight .o { color: #9ca3af; } /* Operator */
|
171
|
+
.dark .highlight .s { color: #fca5a5; } /* String */
|
172
|
+
.dark .highlight .na { color: #fca5a5; } /* Name.Attribute */
|
173
|
+
.dark .highlight .nb { color: #c084fc; } /* Name.Builtin */
|
174
|
+
.dark .highlight .nc { color: #60a5fa; } /* Name.Class */
|
175
|
+
.dark .highlight .nf { color: #34d399; } /* Name.Function */
|
176
|
+
.dark .highlight .nn { color: #60a5fa; } /* Name.Namespace */
|
177
|
+
.dark .highlight .nt { color: #34d399; } /* Name.Tag */
|
178
|
+
.dark .highlight .m,
|
179
|
+
.dark .highlight .mi,
|
180
|
+
.dark .highlight .mf { color: #fb923c; } /* Numbers */
|
181
|
+
|
182
|
+
/* Simple badges */
|
183
|
+
.badge {
|
184
|
+
display: inline-flex;
|
185
|
+
align-items: center;
|
186
|
+
padding: 0.125rem 0.625rem;
|
187
|
+
border-radius: 0.25rem;
|
188
|
+
font-size: 0.75rem;
|
189
|
+
font-weight: 500;
|
190
|
+
text-transform: uppercase;
|
191
|
+
letter-spacing: 0.05em;
|
192
|
+
}
|
193
|
+
|
194
|
+
.badge-get {
|
195
|
+
background-color: #3b82f6;
|
196
|
+
color: white;
|
197
|
+
}
|
198
|
+
|
199
|
+
.badge-post {
|
200
|
+
background-color: #10b981;
|
201
|
+
color: white;
|
202
|
+
}
|
203
|
+
|
204
|
+
.badge-put {
|
205
|
+
background-color: #f97316;
|
206
|
+
color: white;
|
207
|
+
}
|
208
|
+
|
209
|
+
.badge-patch {
|
210
|
+
background-color: #a855f7;
|
211
|
+
color: white;
|
212
|
+
}
|
213
|
+
|
214
|
+
.badge-delete {
|
215
|
+
background-color: #ef4444;
|
216
|
+
color: white;
|
217
|
+
}
|
218
|
+
|
219
|
+
/* Status badges */
|
220
|
+
.status-success {
|
221
|
+
background-color: #dcfce7;
|
222
|
+
color: #166534;
|
223
|
+
}
|
224
|
+
.dark .status-success {
|
225
|
+
background-color: rgba(34, 197, 94, 0.3);
|
226
|
+
color: #86efac;
|
227
|
+
}
|
228
|
+
|
229
|
+
.status-info {
|
230
|
+
background-color: #dbeafe;
|
231
|
+
color: #1e40af;
|
232
|
+
}
|
233
|
+
.dark .status-info {
|
234
|
+
background-color: rgba(59, 130, 246, 0.3);
|
235
|
+
color: #93c5fd;
|
236
|
+
}
|
237
|
+
|
238
|
+
.status-warning {
|
239
|
+
background-color: #fef3c7;
|
240
|
+
color: #92400e;
|
241
|
+
}
|
242
|
+
.dark .status-warning {
|
243
|
+
background-color: rgba(234, 179, 8, 0.3);
|
244
|
+
color: #fde047;
|
245
|
+
}
|
246
|
+
|
247
|
+
.status-error {
|
248
|
+
background-color: #fee2e2;
|
249
|
+
color: #991b1b;
|
250
|
+
}
|
251
|
+
.dark .status-error {
|
252
|
+
background-color: rgba(239, 68, 68, 0.3);
|
253
|
+
color: #fca5a5;
|
254
|
+
}
|
255
|
+
|
256
|
+
/* Cards */
|
257
|
+
.card {
|
258
|
+
background-color: white;
|
259
|
+
border-radius: 0.5rem;
|
260
|
+
border: 1px solid #e5e7eb;
|
261
|
+
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
262
|
+
}
|
263
|
+
.dark .card {
|
264
|
+
background-color: #1f2937;
|
265
|
+
border-color: #374151;
|
266
|
+
}
|
267
|
+
|
268
|
+
/* Buttons */
|
269
|
+
.btn-icon {
|
270
|
+
padding: 0.5rem;
|
271
|
+
border-radius: 0.5rem;
|
272
|
+
}
|
273
|
+
.btn-icon:hover {
|
274
|
+
background-color: #f3f4f6;
|
275
|
+
}
|
276
|
+
.dark .btn-icon:hover {
|
277
|
+
background-color: #374151;
|
278
|
+
}
|
279
|
+
|
280
|
+
/* Base HTML elements styling */
|
281
|
+
input[type="text"],
|
282
|
+
input[type="email"],
|
283
|
+
input[type="password"],
|
284
|
+
input[type="number"],
|
285
|
+
input[type="url"],
|
286
|
+
input[type="search"],
|
287
|
+
textarea,
|
288
|
+
select {
|
289
|
+
width: 100%;
|
290
|
+
padding: 0.5rem 0.75rem;
|
291
|
+
background-color: white;
|
292
|
+
border: 1px solid #d1d5db;
|
293
|
+
border-radius: 0.5rem;
|
294
|
+
color: #111827;
|
295
|
+
}
|
296
|
+
.dark input[type="text"],
|
297
|
+
.dark input[type="email"],
|
298
|
+
.dark input[type="password"],
|
299
|
+
.dark input[type="number"],
|
300
|
+
.dark input[type="url"],
|
301
|
+
.dark input[type="search"],
|
302
|
+
.dark textarea,
|
303
|
+
.dark select {
|
304
|
+
background-color: #1f2937;
|
305
|
+
border-color: #4b5563;
|
306
|
+
color: white;
|
307
|
+
}
|
308
|
+
|
309
|
+
input:focus,
|
310
|
+
textarea:focus,
|
311
|
+
select:focus {
|
312
|
+
outline: none;
|
313
|
+
ring: 2px;
|
314
|
+
ring-color: #3b82f6;
|
315
|
+
border-color: #3b82f6;
|
316
|
+
}
|
317
|
+
|
318
|
+
button[type="submit"],
|
319
|
+
.btn-primary {
|
320
|
+
padding: 0.5rem 1rem;
|
321
|
+
background-color: #2563eb;
|
322
|
+
color: white;
|
323
|
+
border-radius: 0.5rem;
|
324
|
+
font-weight: 500;
|
325
|
+
}
|
326
|
+
button[type="submit"]:hover,
|
327
|
+
.btn-primary:hover {
|
328
|
+
background-color: #1d4ed8;
|
329
|
+
}
|
330
|
+
|
331
|
+
.content a {
|
332
|
+
color: #2563eb;
|
333
|
+
text-decoration: underline;
|
334
|
+
}
|
335
|
+
.dark .content a {
|
336
|
+
color: #60a5fa;
|
337
|
+
}
|
338
|
+
|
339
|
+
table {
|
340
|
+
width: 100%;
|
341
|
+
border-collapse: collapse;
|
342
|
+
}
|
343
|
+
|
344
|
+
th {
|
345
|
+
padding: 0.5rem 1rem;
|
346
|
+
background-color: #f3f4f6;
|
347
|
+
border: 1px solid #e5e7eb;
|
348
|
+
text-align: left;
|
349
|
+
font-weight: 500;
|
350
|
+
}
|
351
|
+
.dark th {
|
352
|
+
background-color: #1f2937;
|
353
|
+
border-color: #374151;
|
354
|
+
}
|
355
|
+
|
356
|
+
td {
|
357
|
+
padding: 0.5rem 1rem;
|
358
|
+
border: 1px solid #e5e7eb;
|
359
|
+
}
|
360
|
+
.dark td {
|
361
|
+
border-color: #374151;
|
362
|
+
}
|
363
|
+
|
364
|
+
pre {
|
365
|
+
background-color: #f3f4f6;
|
366
|
+
padding: 1rem;
|
367
|
+
border-radius: 0.5rem;
|
368
|
+
overflow-x: auto;
|
369
|
+
}
|
370
|
+
.dark pre {
|
371
|
+
background-color: #111827;
|
372
|
+
}
|
373
|
+
|
374
|
+
code {
|
375
|
+
background-color: #f3f4f6;
|
376
|
+
padding: 0.125rem 0.375rem;
|
377
|
+
border-radius: 0.25rem;
|
378
|
+
font-size: 0.875rem;
|
379
|
+
font-family: monospace;
|
380
|
+
}
|
381
|
+
.dark code {
|
382
|
+
background-color: #1f2937;
|
383
|
+
}
|
384
|
+
|
385
|
+
pre code {
|
386
|
+
background: transparent;
|
387
|
+
padding: 0;
|
388
|
+
}
|
389
|
+
|
390
|
+
h1, h2, h3, h4, h5, h6 {
|
391
|
+
font-weight: bold;
|
392
|
+
color: #111827;
|
393
|
+
}
|
394
|
+
.dark h1, .dark h2, .dark h3, .dark h4, .dark h5, .dark h6 {
|
395
|
+
color: white;
|
396
|
+
}
|
397
|
+
|
398
|
+
h1 { font-size: 1.875rem; }
|
399
|
+
h2 { font-size: 1.5rem; }
|
400
|
+
h3 { font-size: 1.25rem; }
|
401
|
+
h4 { font-size: 1.125rem; }
|
402
|
+
|
403
|
+
label {
|
404
|
+
display: block;
|
405
|
+
font-size: 0.875rem;
|
406
|
+
font-weight: 500;
|
407
|
+
color: #374151;
|
408
|
+
margin-bottom: 0.25rem;
|
409
|
+
}
|
410
|
+
.dark label {
|
411
|
+
color: #d1d5db;
|
412
|
+
}
|
413
|
+
|
414
|
+
.form-group {
|
415
|
+
margin-bottom: 1rem;
|
416
|
+
}
|
417
|
+
|
418
|
+
.alert {
|
419
|
+
padding: 1rem;
|
420
|
+
border-radius: 0.5rem;
|
421
|
+
border: 1px solid;
|
422
|
+
}
|
423
|
+
|
424
|
+
.alert-error {
|
425
|
+
background-color: #fef2f2;
|
426
|
+
border-color: #fecaca;
|
427
|
+
color: #991b1b;
|
428
|
+
}
|
429
|
+
.dark .alert-error {
|
430
|
+
background-color: rgba(239, 68, 68, 0.2);
|
431
|
+
border-color: #991b1b;
|
432
|
+
color: #fca5a5;
|
433
|
+
}
|
434
|
+
|
435
|
+
.alert-success {
|
436
|
+
background-color: #f0fdf4;
|
437
|
+
border-color: #bbf7d0;
|
438
|
+
color: #166534;
|
439
|
+
}
|
440
|
+
.dark .alert-success {
|
441
|
+
background-color: rgba(34, 197, 94, 0.2);
|
442
|
+
border-color: #166534;
|
443
|
+
color: #86efac;
|
444
|
+
}
|
445
|
+
|
446
|
+
.alert-info {
|
447
|
+
background-color: #eff6ff;
|
448
|
+
border-color: #bfdbfe;
|
449
|
+
color: #1e40af;
|
450
|
+
}
|
451
|
+
.dark .alert-info {
|
452
|
+
background-color: rgba(59, 130, 246, 0.2);
|
453
|
+
border-color: #1e40af;
|
454
|
+
color: #93c5fd;
|
455
|
+
}
|
456
|
+
</style>
|
457
|
+
|
458
|
+
{% if code_style %}<style>{{ code_style }}</style>{% endif %}
|
459
|
+
</head>
|
460
|
+
|
461
|
+
<body class="min-h-screen bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
462
|
+
|
463
|
+
{# Glass morphism navbar #}
|
464
|
+
<nav class="sticky top-0 z-50 glass border-b border-gray-200/50 dark:border-gray-700/50 shadow-sm">
|
465
|
+
<div class="px-6 py-3">
|
466
|
+
<div class="flex items-center justify-between">
|
467
|
+
|
468
|
+
{# Left: Branding + HTTP Method Badge #}
|
469
|
+
<div class="flex items-center space-x-4">
|
470
|
+
<a href="/" class="text-xl font-bold text-gray-900 dark:text-white hover:text-blue-600 dark:hover:text-blue-400 transition-all">
|
471
|
+
{% block branding %}{{ project_name|default:"API" }}{% endblock %}
|
472
|
+
</a>
|
473
|
+
|
474
|
+
{% if request.method %}
|
475
|
+
<span class="badge
|
476
|
+
{% if request.method == 'GET' %}badge-get
|
477
|
+
{% elif request.method == 'POST' %}badge-post
|
478
|
+
{% elif request.method == 'PUT' %}badge-put
|
479
|
+
{% elif request.method == 'PATCH' %}badge-patch
|
480
|
+
{% elif request.method == 'DELETE' %}badge-delete
|
481
|
+
{% else %}bg-gradient-to-r from-gray-500 to-gray-600 text-white{% endif %}">
|
482
|
+
{{ request.method }}
|
483
|
+
</span>
|
484
|
+
{% endif %}
|
485
|
+
</div>
|
486
|
+
|
487
|
+
{# Right: Actions #}
|
488
|
+
<div class="flex items-center space-x-2">
|
489
|
+
|
490
|
+
{# Command Palette Trigger #}
|
491
|
+
<button @click="commandPaletteOpen = true"
|
492
|
+
class="hidden md:flex items-center space-x-2 px-3 py-1.5 rounded-lg bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-sm transition-all group">
|
493
|
+
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
494
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16l2.879-2.879m0 0a3 3 0 104.243-4.242 3 3 0 00-4.243 4.242zM21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
495
|
+
</svg>
|
496
|
+
<span class="text-gray-600 dark:text-gray-400">Search</span>
|
497
|
+
<kbd class="px-2 py-0.5 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘K</kbd>
|
498
|
+
</button>
|
499
|
+
|
500
|
+
{# Theme Toggle #}
|
501
|
+
<div class="relative" x-data="{ open: false }">
|
502
|
+
<button @click="open = !open"
|
503
|
+
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-all">
|
504
|
+
<svg x-show="theme === 'light'" class="w-5 h-5 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
|
505
|
+
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd"/>
|
506
|
+
</svg>
|
507
|
+
<svg x-show="theme === 'dark'" class="w-5 h-5 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
508
|
+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
509
|
+
</svg>
|
510
|
+
<svg x-show="theme === 'auto'" class="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
511
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"/>
|
512
|
+
</svg>
|
513
|
+
</button>
|
514
|
+
|
515
|
+
{# Theme dropdown #}
|
516
|
+
<div x-show="open"
|
517
|
+
@click.away="open = false"
|
518
|
+
x-transition
|
519
|
+
class="absolute right-0 mt-2 w-40 glass rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 py-1">
|
520
|
+
<button @click="setTheme('light'); open = false"
|
521
|
+
class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
|
522
|
+
<svg class="w-4 h-4 mr-2 text-yellow-500" fill="currentColor" viewBox="0 0 20 20">
|
523
|
+
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
|
524
|
+
</svg>
|
525
|
+
Light
|
526
|
+
</button>
|
527
|
+
<button @click="setTheme('dark'); open = false"
|
528
|
+
class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
|
529
|
+
<svg class="w-4 h-4 mr-2 text-blue-400" fill="currentColor" viewBox="0 0 20 20">
|
530
|
+
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"/>
|
531
|
+
</svg>
|
532
|
+
Dark
|
533
|
+
</button>
|
534
|
+
<button @click="setTheme('auto'); open = false"
|
535
|
+
class="flex items-center w-full px-4 py-2 text-sm hover:bg-gray-100 dark:hover:bg-gray-800">
|
536
|
+
<svg class="w-4 h-4 mr-2 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
537
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3"/>
|
538
|
+
</svg>
|
539
|
+
Auto
|
540
|
+
</button>
|
541
|
+
</div>
|
542
|
+
</div>
|
543
|
+
|
544
|
+
{# User Menu #}
|
545
|
+
<div class="flex items-center space-x-3 pl-3 border-l border-gray-200 dark:border-gray-700">
|
546
|
+
{% if user.is_authenticated %}
|
547
|
+
<div class="flex items-center space-x-3">
|
548
|
+
<div class="flex items-center space-x-2">
|
549
|
+
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-semibold text-sm">
|
550
|
+
{{ user.username|first|upper }}
|
551
|
+
</div>
|
552
|
+
<span class="hidden md:block text-sm font-medium text-gray-900 dark:text-white">{{ user.username }}</span>
|
553
|
+
</div>
|
554
|
+
{% if api_settings.LOGOUT_URL %}
|
555
|
+
<form action="{% url api_settings.LOGOUT_URL %}" method="post">
|
556
|
+
{% csrf_token %}
|
557
|
+
<button type="submit" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">
|
558
|
+
Logout
|
559
|
+
</button>
|
560
|
+
</form>
|
561
|
+
{% endif %}
|
562
|
+
</div>
|
563
|
+
{% else %}
|
564
|
+
{% optional_login request %}
|
565
|
+
{% endif %}
|
566
|
+
</div>
|
567
|
+
</div>
|
568
|
+
</div>
|
569
|
+
</div>
|
570
|
+
</nav>
|
571
|
+
|
572
|
+
{# Main Content #}
|
573
|
+
<main class="container mx-auto px-4 py-6 max-w-7xl">
|
574
|
+
|
575
|
+
{# Breadcrumbs with animation #}
|
576
|
+
{% if breadcrumblist %}
|
577
|
+
<nav class="mb-6 animate-fade-in">
|
578
|
+
<ol class="flex items-center space-x-2 text-sm">
|
579
|
+
{% for breadcrumb_name, breadcrumb_url in breadcrumblist %}
|
580
|
+
{% if not forloop.last %}
|
581
|
+
<li class="flex items-center">
|
582
|
+
<a href="{{ breadcrumb_url }}"
|
583
|
+
class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline">
|
584
|
+
{{ breadcrumb_name }}
|
585
|
+
</a>
|
586
|
+
<svg class="w-4 h-4 mx-2 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
587
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
|
588
|
+
</svg>
|
589
|
+
</li>
|
590
|
+
{% else %}
|
591
|
+
<li class="text-gray-900 dark:text-gray-100 font-medium">{{ breadcrumb_name }}</li>
|
592
|
+
{% endif %}
|
593
|
+
{% endfor %}
|
594
|
+
</ol>
|
595
|
+
</nav>
|
596
|
+
{% endif %}
|
597
|
+
|
598
|
+
{# Content Block #}
|
599
|
+
<div class="space-y-6">
|
600
|
+
{% block content %}{% endblock %}
|
601
|
+
</div>
|
602
|
+
|
603
|
+
{# Footer #}
|
604
|
+
{% load django_cfg %}
|
605
|
+
<footer class="mt-12 pt-8 border-t border-gray-200 dark:border-gray-700">
|
606
|
+
<div class="text-center">
|
607
|
+
<p class="text-sm text-gray-600 dark:text-gray-400">
|
608
|
+
Powered by
|
609
|
+
<a href="{% lib_site_url %}"
|
610
|
+
class="text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium transition-colors"
|
611
|
+
target="_blank" rel="noopener noreferrer">
|
612
|
+
{% lib_name %}
|
613
|
+
</a>
|
614
|
+
</p>
|
615
|
+
<p class="mt-2 text-xs text-gray-500 dark:text-gray-500">
|
616
|
+
{% lib_subtitle %}
|
617
|
+
</p>
|
618
|
+
</div>
|
619
|
+
</footer>
|
620
|
+
</main>
|
621
|
+
|
622
|
+
{# Command Palette #}
|
623
|
+
<div x-show="commandPaletteOpen"
|
624
|
+
x-transition.opacity
|
625
|
+
@click="commandPaletteOpen = false"
|
626
|
+
class="fixed inset-0 bg-black/50 backdrop-blur-sm z-50">
|
627
|
+
|
628
|
+
<div @click.stop
|
629
|
+
class="fixed top-20 left-1/2 -translate-x-1/2 w-full max-w-2xl glass rounded-xl shadow-2xl border border-gray-200 dark:border-gray-700 overflow-hidden">
|
630
|
+
|
631
|
+
{# Search Input #}
|
632
|
+
<div class="p-4 border-b border-gray-200 dark:border-gray-700">
|
633
|
+
<div class="relative">
|
634
|
+
<svg class="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
635
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"/>
|
636
|
+
</svg>
|
637
|
+
<input type="text"
|
638
|
+
x-model="commandSearch"
|
639
|
+
@keydown.escape="commandPaletteOpen = false"
|
640
|
+
placeholder="Search actions, endpoints..."
|
641
|
+
class="w-full pl-10 pr-4 py-3 bg-transparent border-0 focus:outline-none text-lg">
|
642
|
+
</div>
|
643
|
+
</div>
|
644
|
+
|
645
|
+
{# Quick Actions #}
|
646
|
+
<div class="p-2 max-h-96 overflow-y-auto">
|
647
|
+
<div class="space-y-1">
|
648
|
+
<button @click="copyCurrentURL(); commandPaletteOpen = false"
|
649
|
+
class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
|
650
|
+
<svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
651
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
652
|
+
</svg>
|
653
|
+
<div class="flex-1">
|
654
|
+
<div class="font-medium">Copy Current URL</div>
|
655
|
+
<div class="text-sm text-gray-500">Copy this endpoint URL to clipboard</div>
|
656
|
+
</div>
|
657
|
+
<kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘C</kbd>
|
658
|
+
</button>
|
659
|
+
|
660
|
+
<button @click="toggleTheme(); commandPaletteOpen = false"
|
661
|
+
class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
|
662
|
+
<svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-yellow-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
663
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
|
664
|
+
</svg>
|
665
|
+
<div class="flex-1">
|
666
|
+
<div class="font-medium">Toggle Theme</div>
|
667
|
+
<div class="text-sm text-gray-500">Switch between light and dark mode</div>
|
668
|
+
</div>
|
669
|
+
<kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">⌘D</kbd>
|
670
|
+
</button>
|
671
|
+
|
672
|
+
<button @click="showShortcuts(); commandPaletteOpen = false"
|
673
|
+
class="w-full flex items-center px-4 py-3 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-left group">
|
674
|
+
<svg class="w-5 h-5 mr-3 text-gray-400 group-hover:text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
675
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
676
|
+
</svg>
|
677
|
+
<div class="flex-1">
|
678
|
+
<div class="font-medium">Keyboard Shortcuts</div>
|
679
|
+
<div class="text-sm text-gray-500">View all available shortcuts</div>
|
680
|
+
</div>
|
681
|
+
<kbd class="px-2 py-1 text-xs bg-gray-200 dark:bg-gray-700 rounded">?</kbd>
|
682
|
+
</button>
|
683
|
+
</div>
|
684
|
+
</div>
|
685
|
+
</div>
|
686
|
+
</div>
|
687
|
+
|
688
|
+
{# Toast Notifications #}
|
689
|
+
<div class="fixed bottom-4 right-4 z-50 space-y-2">
|
690
|
+
<template x-for="(toast, index) in toasts" :key="index">
|
691
|
+
<div x-show="toast.show"
|
692
|
+
x-transition:enter="transition ease-out duration-300"
|
693
|
+
x-transition:enter-start="opacity-0 translate-y-4"
|
694
|
+
x-transition:enter-end="opacity-100 translate-y-0"
|
695
|
+
x-transition:leave="transition ease-in duration-200"
|
696
|
+
x-transition:leave-start="opacity-100 translate-y-0"
|
697
|
+
x-transition:leave-end="opacity-0 translate-y-4"
|
698
|
+
class="glass rounded-lg shadow-xl border border-gray-200 dark:border-gray-700 p-4 flex items-center space-x-3 max-w-sm">
|
699
|
+
|
700
|
+
<svg x-show="toast.type === 'success'" class="w-5 h-5 text-green-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
701
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
|
702
|
+
</svg>
|
703
|
+
|
704
|
+
<svg x-show="toast.type === 'error'" class="w-5 h-5 text-red-500 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
|
705
|
+
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
|
706
|
+
</svg>
|
707
|
+
|
708
|
+
<p class="flex-1 text-sm" x-text="toast.message"></p>
|
709
|
+
|
710
|
+
<button @click="removeToast(index)" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
711
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
712
|
+
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
|
713
|
+
</svg>
|
714
|
+
</button>
|
715
|
+
</div>
|
716
|
+
</template>
|
717
|
+
</div>
|
718
|
+
|
719
|
+
{# Scripts #}
|
720
|
+
{# jQuery required for DRF ajax-form.js #}
|
721
|
+
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
|
722
|
+
|
723
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/prism.min.js"></script>
|
724
|
+
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.29.0/components/prism-json.min.js"></script>
|
725
|
+
<script src="{% static 'rest_framework/js/csrf.js' %}"></script>
|
726
|
+
<script src="{% static 'rest_framework/js/ajax-form.js' %}"></script>
|
727
|
+
|
728
|
+
<script>
|
729
|
+
function drfApp() {
|
730
|
+
return {
|
731
|
+
theme: '{{ theme|default:"auto" }}',
|
732
|
+
commandPaletteOpen: false,
|
733
|
+
commandSearch: '',
|
734
|
+
toasts: [],
|
735
|
+
|
736
|
+
init() {
|
737
|
+
this.applyTheme();
|
738
|
+
this.listenForSystemTheme();
|
739
|
+
},
|
740
|
+
|
741
|
+
applyTheme() {
|
742
|
+
let isDark = false;
|
743
|
+
|
744
|
+
if (this.theme === 'dark') {
|
745
|
+
document.documentElement.classList.add('dark');
|
746
|
+
isDark = true;
|
747
|
+
} else if (this.theme === 'light') {
|
748
|
+
document.documentElement.classList.remove('dark');
|
749
|
+
isDark = false;
|
750
|
+
} else {
|
751
|
+
// Auto: use system preference
|
752
|
+
isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
753
|
+
if (isDark) {
|
754
|
+
document.documentElement.classList.add('dark');
|
755
|
+
} else {
|
756
|
+
document.documentElement.classList.remove('dark');
|
757
|
+
}
|
758
|
+
}
|
759
|
+
|
760
|
+
// Switch Prism.js theme
|
761
|
+
const prismLight = document.getElementById('prism-light');
|
762
|
+
const prismDark = document.getElementById('prism-dark');
|
763
|
+
if (prismLight && prismDark) {
|
764
|
+
prismLight.disabled = isDark;
|
765
|
+
prismDark.disabled = !isDark;
|
766
|
+
}
|
767
|
+
},
|
768
|
+
|
769
|
+
setTheme(newTheme) {
|
770
|
+
this.theme = newTheme;
|
771
|
+
this.applyTheme();
|
772
|
+
document.cookie = `theme=${newTheme}; path=/; max-age=31536000; SameSite=Lax`;
|
773
|
+
this.showToast(`Theme changed to ${newTheme}`, 'success');
|
774
|
+
},
|
775
|
+
|
776
|
+
toggleTheme() {
|
777
|
+
const themes = ['light', 'dark', 'auto'];
|
778
|
+
const currentIndex = themes.indexOf(this.theme);
|
779
|
+
const nextTheme = themes[(currentIndex + 1) % themes.length];
|
780
|
+
this.setTheme(nextTheme);
|
781
|
+
},
|
782
|
+
|
783
|
+
listenForSystemTheme() {
|
784
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
785
|
+
if (this.theme === 'auto') {
|
786
|
+
this.applyTheme();
|
787
|
+
}
|
788
|
+
});
|
789
|
+
},
|
790
|
+
|
791
|
+
handleKeyboard(event) {
|
792
|
+
// Command Palette (Cmd/Ctrl + K)
|
793
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
|
794
|
+
event.preventDefault();
|
795
|
+
this.commandPaletteOpen = !this.commandPaletteOpen;
|
796
|
+
}
|
797
|
+
|
798
|
+
// Toggle theme (Cmd/Ctrl + D)
|
799
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'd') {
|
800
|
+
event.preventDefault();
|
801
|
+
this.toggleTheme();
|
802
|
+
}
|
803
|
+
|
804
|
+
// Copy URL (Cmd/Ctrl + C when not in input and no text selected)
|
805
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 'c' && !['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
|
806
|
+
// Only intercept if no text is selected
|
807
|
+
const selection = window.getSelection();
|
808
|
+
if (!selection || selection.toString().length === 0) {
|
809
|
+
event.preventDefault();
|
810
|
+
this.copyCurrentURL();
|
811
|
+
}
|
812
|
+
}
|
813
|
+
|
814
|
+
// Show shortcuts (?)
|
815
|
+
if (event.key === '?' && !['INPUT', 'TEXTAREA'].includes(event.target.tagName)) {
|
816
|
+
event.preventDefault();
|
817
|
+
this.showShortcuts();
|
818
|
+
}
|
819
|
+
|
820
|
+
// Close command palette (Escape)
|
821
|
+
if (event.key === 'Escape') {
|
822
|
+
this.commandPaletteOpen = false;
|
823
|
+
}
|
824
|
+
},
|
825
|
+
|
826
|
+
copyCurrentURL() {
|
827
|
+
navigator.clipboard.writeText(window.location.href).then(() => {
|
828
|
+
this.showToast('URL copied to clipboard!', 'success');
|
829
|
+
}).catch(() => {
|
830
|
+
this.showToast('Failed to copy URL', 'error');
|
831
|
+
});
|
832
|
+
},
|
833
|
+
|
834
|
+
showShortcuts() {
|
835
|
+
const shortcuts = `
|
836
|
+
Keyboard Shortcuts:
|
837
|
+
|
838
|
+
⌘K or Ctrl+K - Open command palette
|
839
|
+
⌘D or Ctrl+D - Toggle theme
|
840
|
+
⌘C or Ctrl+C - Copy current URL
|
841
|
+
? - Show this help
|
842
|
+
Esc - Close dialogs
|
843
|
+
`.trim();
|
844
|
+
|
845
|
+
alert(shortcuts);
|
846
|
+
},
|
847
|
+
|
848
|
+
showToast(message, type = 'success') {
|
849
|
+
const toast = { message, type, show: true };
|
850
|
+
this.toasts.push(toast);
|
851
|
+
|
852
|
+
setTimeout(() => {
|
853
|
+
const index = this.toasts.indexOf(toast);
|
854
|
+
if (index > -1) {
|
855
|
+
this.toasts[index].show = false;
|
856
|
+
setTimeout(() => this.removeToast(index), 300);
|
857
|
+
}
|
858
|
+
}, 3000);
|
859
|
+
},
|
860
|
+
|
861
|
+
removeToast(index) {
|
862
|
+
this.toasts.splice(index, 1);
|
863
|
+
}
|
864
|
+
}
|
865
|
+
}
|
866
|
+
|
867
|
+
// Global JSON rendering function
|
868
|
+
function renderJSON(data, level = 0) {
|
869
|
+
if (data === null) return '<span class="text-gray-500 dark:text-gray-400">null</span>';
|
870
|
+
if (data === undefined) return '<span class="text-gray-500 dark:text-gray-400">undefined</span>';
|
871
|
+
if (typeof data === 'boolean') return `<span class="text-purple-600 dark:text-purple-400">${data}</span>`;
|
872
|
+
if (typeof data === 'number') return `<span class="text-orange-600 dark:text-orange-400">${data}</span>`;
|
873
|
+
if (typeof data === 'string') return `<span class="text-green-600 dark:text-green-400">"${escapeHtml(data)}"</span>`;
|
874
|
+
|
875
|
+
const indent = ' '.repeat(level);
|
876
|
+
const childIndent = ' '.repeat(level + 1);
|
877
|
+
|
878
|
+
if (Array.isArray(data)) {
|
879
|
+
if (data.length === 0) return '<span class="text-gray-600 dark:text-gray-400">[]</span>';
|
880
|
+
|
881
|
+
const id = 'json-' + Math.random().toString(36).substr(2, 9);
|
882
|
+
let html = `<div class="json-node">
|
883
|
+
<span class="json-toggle cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-700 px-1 rounded"
|
884
|
+
onclick="this.dataset.expanded = this.dataset.expanded === 'true' ? 'false' : 'true';
|
885
|
+
this.textContent = this.dataset.expanded === 'true' ? '▼' : '▶';
|
886
|
+
document.getElementById('${id}').style.display = this.dataset.expanded === 'true' ? 'block' : 'none'"
|
887
|
+
data-expanded="false">▶</span>
|
888
|
+
<span class="text-gray-600 dark:text-gray-400">[</span>
|
889
|
+
<span class="text-xs text-gray-500 dark:text-gray-500 ml-2">${data.length} items</span>
|
890
|
+
<div id="${id}" class="json-content ml-4" style="display: none;">`;
|
891
|
+
|
892
|
+
data.forEach((item, i) => {
|
893
|
+
html += `<div class="py-0.5">${childIndent}${renderJSON(item, level + 1)}${i < data.length - 1 ? '<span class="text-gray-600 dark:text-gray-400">,</span>' : ''}</div>`;
|
894
|
+
});
|
895
|
+
|
896
|
+
html += `</div>${indent}<span class="text-gray-600 dark:text-gray-400">]</span></div>`;
|
897
|
+
return html;
|
898
|
+
}
|
899
|
+
|
900
|
+
if (typeof data === 'object') {
|
901
|
+
const keys = Object.keys(data);
|
902
|
+
if (keys.length === 0) return '<span class="text-gray-600 dark:text-gray-400">{}</span>';
|
903
|
+
|
904
|
+
const id = 'json-' + Math.random().toString(36).substr(2, 9);
|
905
|
+
let html = `<div class="json-node">
|
906
|
+
<span class="json-toggle cursor-pointer select-none hover:bg-gray-200 dark:hover:bg-gray-700 px-1 rounded"
|
907
|
+
onclick="this.dataset.expanded = this.dataset.expanded === 'true' ? 'false' : 'true';
|
908
|
+
this.textContent = this.dataset.expanded === 'true' ? '▼' : '▶';
|
909
|
+
document.getElementById('${id}').style.display = this.dataset.expanded === 'true' ? 'block' : 'none'"
|
910
|
+
data-expanded="false">▶</span>
|
911
|
+
<span class="text-gray-600 dark:text-gray-400">{</span>
|
912
|
+
<span class="text-xs text-gray-500 dark:text-gray-500 ml-2">${keys.length} keys</span>
|
913
|
+
<div id="${id}" class="json-content ml-4" style="display: none;">`;
|
914
|
+
|
915
|
+
keys.forEach((key, i) => {
|
916
|
+
html += `<div class="py-0.5">${childIndent}<span class="text-blue-600 dark:text-blue-400">"${escapeHtml(key)}"</span><span class="text-gray-600 dark:text-gray-400">: </span>${renderJSON(data[key], level + 1)}${i < keys.length - 1 ? '<span class="text-gray-600 dark:text-gray-400">,</span>' : ''}</div>`;
|
917
|
+
});
|
918
|
+
|
919
|
+
html += `</div>${indent}<span class="text-gray-600 dark:text-gray-400">}</span></div>`;
|
920
|
+
return html;
|
921
|
+
}
|
922
|
+
|
923
|
+
return String(data);
|
924
|
+
}
|
925
|
+
|
926
|
+
function escapeHtml(text) {
|
927
|
+
const div = document.createElement('div');
|
928
|
+
div.textContent = text;
|
929
|
+
return div.innerHTML;
|
930
|
+
}
|
931
|
+
</script>
|
932
|
+
|
933
|
+
{% if django_browser_reload_enabled %}
|
934
|
+
<script src="{% static 'django_browser_reload/reload-listener.js' %}"></script>
|
935
|
+
{% endif %}
|
936
|
+
|
937
|
+
</body>
|
938
|
+
</html>
|