django-cfg 1.4.88__py3-none-any.whl → 1.4.89__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.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/centrifugo/views/__init__.py +0 -2
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/apps/dashboard/services/overview_service.py +205 -0
- django_cfg/apps/frontend/test_routing.py +134 -0
- django_cfg/apps/frontend/views.py +69 -28
- django_cfg/apps/urls.py +0 -1
- django_cfg/core/builders/apps_builder.py +0 -58
- django_cfg/modules/django_unfold/__init__.py +5 -24
- django_cfg/modules/django_unfold/models/__init__.py +0 -23
- django_cfg/modules/django_unfold/models/config.py +11 -65
- django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
- django_cfg/modules/django_unfold/tailwind.py +2 -4
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/third_party.py +0 -9
- django_cfg/routing/callbacks.py +1 -43
- django_cfg/static/frontend/admin/404/index.html +1 -1
- django_cfg/static/frontend/admin/404.html +1 -1
- django_cfg/static/frontend/admin/500/index.html +1 -1
- django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-1c0fff0f59a6d683.js → _app-c7dcd3aa616fab68.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-44a8313fa040e9ad.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
- django_cfg/static/frontend/admin/auth/index.html +1 -1
- django_cfg/static/frontend/admin/index.html +1 -1
- django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
- django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
- django_cfg/static/frontend/admin/legal/security/index.html +1 -1
- django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
- django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
- django_cfg/static/frontend/admin/private/index.html +1 -1
- django_cfg/static/frontend/admin/private/profile/index.html +1 -1
- django_cfg/static/frontend/admin/private/ui/index.html +2 -2
- {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
- {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/RECORD +39 -143
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
- django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
- django_cfg/apps/centrifugo/urls_admin.py +0 -20
- django_cfg/apps/centrifugo/views/dashboard.py +0 -28
- django_cfg/modules/django_dashboard/__init__.py +0 -23
- django_cfg/modules/django_dashboard/components.py +0 -312
- django_cfg/modules/django_dashboard/debug.py +0 -174
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
- django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
- django_cfg/modules/django_dashboard/sections/base.py +0 -129
- django_cfg/modules/django_dashboard/sections/commands.py +0 -33
- django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
- django_cfg/modules/django_dashboard/sections/overview.py +0 -398
- django_cfg/modules/django_dashboard/sections/stats.py +0 -48
- django_cfg/modules/django_dashboard/sections/system.py +0 -74
- django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
- django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
- django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
- django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
- django_cfg/modules/django_unfold/callbacks/base.py +0 -290
- django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
- django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
- django_cfg/modules/django_unfold/callbacks/main.py +0 -322
- django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
- django_cfg/modules/django_unfold/callbacks/system.py +0 -180
- django_cfg/modules/django_unfold/callbacks/users.py +0 -65
- django_cfg/modules/django_unfold/models/dashboard.py +0 -207
- django_cfg/modules/django_unfold/models/tabs.py +0 -26
- django_cfg/modules/django_unfold/models.py +0 -98
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
- django_cfg/static/frontend/admin/_next/static/chunks/50314-5ec79b293c2283dd.js +0 -1
- django_cfg/templates/admin/sections/commands_section.html +0 -5
- django_cfg/templates/admin/sections/documentation_section.html +0 -5
- django_cfg/templates/admin/sections/overview_section.html +0 -5
- django_cfg/templates/admin/sections/stats_section.html +0 -5
- django_cfg/templates/admin/sections/system_section.html +0 -5
- django_cfg/templates/admin/sections/widgets_section.html +0 -11
- django_cfg/templates/admin_old/components/action_grid.html +0 -49
- django_cfg/templates/admin_old/components/card.html +0 -50
- django_cfg/templates/admin_old/components/data_table.html +0 -67
- django_cfg/templates/admin_old/components/metric_card.html +0 -39
- django_cfg/templates/admin_old/components/modal.html +0 -58
- django_cfg/templates/admin_old/components/progress_bar.html +0 -20
- django_cfg/templates/admin_old/components/section_header.html +0 -26
- django_cfg/templates/admin_old/components/stat_item.html +0 -32
- django_cfg/templates/admin_old/components/stats_grid.html +0 -72
- django_cfg/templates/admin_old/components/status_badge.html +0 -28
- django_cfg/templates/admin_old/components/user_avatar.html +0 -27
- django_cfg/templates/admin_old/constance/change_list.html +0 -74
- django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
- django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
- django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
- django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
- django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
- django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
- django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
- django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
- django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
- django_cfg/templates/admin_old/index.html +0 -80
- django_cfg/templates/admin_old/index_new.html +0 -119
- django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
- django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
- django_cfg/templates/admin_old/sections/commands_section.html +0 -549
- django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
- django_cfg/templates/admin_old/sections/overview_section.html +0 -112
- django_cfg/templates/admin_old/sections/stats_section.html +0 -35
- django_cfg/templates/admin_old/sections/system_section.html +0 -99
- django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
- django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
- django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
- django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
- django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
- django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
- django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
- django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
- django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
- django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
- django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
- django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
- django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
- django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
- django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
- django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
- django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
- django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
- django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
- django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
- /django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
- {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Centrifugo Template Tags.
|
|
3
|
-
|
|
4
|
-
Provides template tags for accessing Centrifugo configuration in templates.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from django import template
|
|
8
|
-
from ..services import get_centrifugo_config
|
|
9
|
-
|
|
10
|
-
register = template.Library()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@register.simple_tag
|
|
14
|
-
def centrifugo_admin_url():
|
|
15
|
-
"""
|
|
16
|
-
Get Centrifugo admin UI URL from configuration.
|
|
17
|
-
|
|
18
|
-
Returns the HTTP admin URL from centrifugo_api_url.
|
|
19
|
-
Example: http://localhost:8002
|
|
20
|
-
|
|
21
|
-
Usage in template:
|
|
22
|
-
{% load centrifugo_tags %}
|
|
23
|
-
{% centrifugo_admin_url %}
|
|
24
|
-
"""
|
|
25
|
-
config = get_centrifugo_config()
|
|
26
|
-
if not config or not config.centrifugo_api_url:
|
|
27
|
-
return ""
|
|
28
|
-
|
|
29
|
-
# Return HTTP API URL (admin UI is on same host)
|
|
30
|
-
url = config.centrifugo_api_url
|
|
31
|
-
|
|
32
|
-
# Remove /api suffix if present
|
|
33
|
-
if url.endswith('/api'):
|
|
34
|
-
url = url[:-len('/api')]
|
|
35
|
-
|
|
36
|
-
# Remove trailing slash
|
|
37
|
-
url = url.rstrip('/')
|
|
38
|
-
|
|
39
|
-
return url
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
@register.simple_tag
|
|
43
|
-
def centrifugo_is_configured():
|
|
44
|
-
"""
|
|
45
|
-
Check if Centrifugo is configured.
|
|
46
|
-
|
|
47
|
-
Returns True if Centrifugo config exists and has URL.
|
|
48
|
-
|
|
49
|
-
Usage in template:
|
|
50
|
-
{% load centrifugo_tags %}
|
|
51
|
-
{% centrifugo_is_configured as is_configured %}
|
|
52
|
-
{% if is_configured %}...{% endif %}
|
|
53
|
-
"""
|
|
54
|
-
config = get_centrifugo_config()
|
|
55
|
-
return bool(config and config.centrifugo_url)
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
@register.simple_tag
|
|
59
|
-
def centrifugo_wrapper_url():
|
|
60
|
-
"""
|
|
61
|
-
Get Centrifugo wrapper URL from configuration.
|
|
62
|
-
|
|
63
|
-
Returns the wrapper URL (our Django proxy).
|
|
64
|
-
Example: http://localhost:8080
|
|
65
|
-
|
|
66
|
-
Usage in template:
|
|
67
|
-
{% load centrifugo_tags %}
|
|
68
|
-
{% centrifugo_wrapper_url %}
|
|
69
|
-
"""
|
|
70
|
-
config = get_centrifugo_config()
|
|
71
|
-
if not config or not config.wrapper_url:
|
|
72
|
-
return ""
|
|
73
|
-
|
|
74
|
-
return config.wrapper_url.rstrip('/')
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
__all__ = [
|
|
78
|
-
"centrifugo_admin_url",
|
|
79
|
-
"centrifugo_is_configured",
|
|
80
|
-
"centrifugo_wrapper_url",
|
|
81
|
-
]
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Admin URLs for Centrifugo dashboard.
|
|
3
|
-
|
|
4
|
-
Dashboard interface for monitoring Centrifugo publish activity.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from django.urls import include, path
|
|
8
|
-
|
|
9
|
-
from .views import dashboard_view
|
|
10
|
-
|
|
11
|
-
app_name = 'django_cfg_centrifugo_admin'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
urlpatterns = [
|
|
15
|
-
# Dashboard page
|
|
16
|
-
path('', dashboard_view, name='dashboard'),
|
|
17
|
-
|
|
18
|
-
# Include API endpoints for dashboard AJAX calls
|
|
19
|
-
path('api/', include('django_cfg.apps.centrifugo.urls')),
|
|
20
|
-
]
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Dashboard view for Centrifugo monitoring.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
from django.contrib.admin.views.decorators import staff_member_required
|
|
6
|
-
from django.shortcuts import render
|
|
7
|
-
from django.urls import reverse
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@staff_member_required
|
|
11
|
-
def dashboard_view(request):
|
|
12
|
-
"""Render the Centrifugo dashboard template."""
|
|
13
|
-
|
|
14
|
-
# Navigation items for the navbar
|
|
15
|
-
nav_items = [
|
|
16
|
-
{
|
|
17
|
-
'label': 'Logs',
|
|
18
|
-
'url': reverse('admin:django_cfg_centrifugo_centrifugolog_changelist'),
|
|
19
|
-
'icon': 'list_alt',
|
|
20
|
-
'active': False,
|
|
21
|
-
},
|
|
22
|
-
]
|
|
23
|
-
|
|
24
|
-
context = {
|
|
25
|
-
'page_title': 'Centrifugo Monitor Dashboard',
|
|
26
|
-
'centrifugo_nav_items': nav_items,
|
|
27
|
-
}
|
|
28
|
-
return render(request, 'django_cfg_centrifugo/pages/dashboard.html', context)
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django CFG Dashboard Module.
|
|
3
|
-
|
|
4
|
-
Section-based architecture for dashboard rendering.
|
|
5
|
-
Inspired by Unfold's clean component approach.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
# Import components to register them with Unfold
|
|
9
|
-
# The @register_component decorator runs on import
|
|
10
|
-
from . import components # noqa: F401
|
|
11
|
-
from .sections.base import DashboardSection
|
|
12
|
-
from .sections.commands import CommandsSection
|
|
13
|
-
from .sections.overview import OverviewSection
|
|
14
|
-
from .sections.stats import StatsSection
|
|
15
|
-
from .sections.system import SystemSection
|
|
16
|
-
|
|
17
|
-
__all__ = [
|
|
18
|
-
'DashboardSection',
|
|
19
|
-
'OverviewSection',
|
|
20
|
-
'StatsSection',
|
|
21
|
-
'SystemSection',
|
|
22
|
-
'CommandsSection',
|
|
23
|
-
]
|
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Dashboard Component Classes
|
|
3
|
-
|
|
4
|
-
Uses Unfold's Component Class system for data preprocessing.
|
|
5
|
-
Separates business logic from presentation templates.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from typing import Any, Dict
|
|
9
|
-
|
|
10
|
-
from unfold.components import BaseComponent, register_component
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@register_component
|
|
14
|
-
class SystemMetricsComponent(BaseComponent):
|
|
15
|
-
"""
|
|
16
|
-
System metrics component for dashboard.
|
|
17
|
-
|
|
18
|
-
Provides health metrics for database, cache, storage, and API.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
def get_context_data(self, **kwargs):
|
|
22
|
-
"""Prepare system metrics data."""
|
|
23
|
-
context = super().get_context_data(**kwargs)
|
|
24
|
-
context.update({
|
|
25
|
-
"data": self.get_system_metrics()
|
|
26
|
-
})
|
|
27
|
-
return context
|
|
28
|
-
|
|
29
|
-
def get_system_metrics(self) -> Dict[str, Any]:
|
|
30
|
-
"""Fetch system health metrics."""
|
|
31
|
-
import shutil
|
|
32
|
-
|
|
33
|
-
from django.core.cache import cache
|
|
34
|
-
from django.db import connection
|
|
35
|
-
|
|
36
|
-
metrics = {}
|
|
37
|
-
|
|
38
|
-
# Database Health
|
|
39
|
-
try:
|
|
40
|
-
with connection.cursor() as cursor:
|
|
41
|
-
cursor.execute("SELECT 1")
|
|
42
|
-
metrics["database"] = {
|
|
43
|
-
"value": 95,
|
|
44
|
-
"title": "Database Health",
|
|
45
|
-
"description": "Connection successful",
|
|
46
|
-
}
|
|
47
|
-
except Exception as e:
|
|
48
|
-
metrics["database"] = {
|
|
49
|
-
"value": 0,
|
|
50
|
-
"title": "Database Health",
|
|
51
|
-
"description": f"Error: {str(e)[:50]}",
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
# Cache Performance
|
|
55
|
-
try:
|
|
56
|
-
cache.set("health_check", "ok", 10)
|
|
57
|
-
if cache.get("health_check") == "ok":
|
|
58
|
-
metrics["cache"] = {
|
|
59
|
-
"value": 90,
|
|
60
|
-
"title": "Cache Performance",
|
|
61
|
-
"description": "Cache working properly",
|
|
62
|
-
}
|
|
63
|
-
else:
|
|
64
|
-
metrics["cache"] = {
|
|
65
|
-
"value": 50,
|
|
66
|
-
"title": "Cache Performance",
|
|
67
|
-
"description": "Cache response delayed",
|
|
68
|
-
}
|
|
69
|
-
except Exception as e:
|
|
70
|
-
metrics["cache"] = {
|
|
71
|
-
"value": 0,
|
|
72
|
-
"title": "Cache Performance",
|
|
73
|
-
"description": f"Error: {str(e)[:50]}",
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
# Storage Space
|
|
77
|
-
try:
|
|
78
|
-
total, used, free = shutil.disk_usage("/")
|
|
79
|
-
free_percentage = int((free / total) * 100)
|
|
80
|
-
usage_percentage = 100 - free_percentage
|
|
81
|
-
|
|
82
|
-
metrics["storage"] = {
|
|
83
|
-
"value": free_percentage,
|
|
84
|
-
"title": "Disk Space",
|
|
85
|
-
"description": f"{free_percentage}% free ({usage_percentage}% used)",
|
|
86
|
-
}
|
|
87
|
-
except Exception as e:
|
|
88
|
-
metrics["storage"] = {
|
|
89
|
-
"value": 0,
|
|
90
|
-
"title": "Disk Space",
|
|
91
|
-
"description": f"Error: {str(e)[:50]}",
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
# API Health
|
|
95
|
-
try:
|
|
96
|
-
from django.urls import get_resolver
|
|
97
|
-
resolver = get_resolver()
|
|
98
|
-
url_patterns = list(resolver.url_patterns)
|
|
99
|
-
|
|
100
|
-
metrics["api"] = {
|
|
101
|
-
"value": 100,
|
|
102
|
-
"title": "REST API",
|
|
103
|
-
"description": f"{len(url_patterns)} URL patterns",
|
|
104
|
-
}
|
|
105
|
-
except Exception:
|
|
106
|
-
metrics["api"] = {
|
|
107
|
-
"value": 50,
|
|
108
|
-
"title": "REST API",
|
|
109
|
-
"description": "Unable to count URLs",
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
return metrics
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
@register_component
|
|
116
|
-
class RecentUsersComponent(BaseComponent):
|
|
117
|
-
"""
|
|
118
|
-
Recent users table component.
|
|
119
|
-
|
|
120
|
-
Provides formatted table data for displaying recent user registrations.
|
|
121
|
-
"""
|
|
122
|
-
|
|
123
|
-
def get_context_data(self, **kwargs):
|
|
124
|
-
"""Prepare recent users table data."""
|
|
125
|
-
context = super().get_context_data(**kwargs)
|
|
126
|
-
|
|
127
|
-
from django.contrib.auth import get_user_model
|
|
128
|
-
User = get_user_model()
|
|
129
|
-
|
|
130
|
-
try:
|
|
131
|
-
recent_users = User.objects.order_by('-date_joined')[:10]
|
|
132
|
-
|
|
133
|
-
context.update({
|
|
134
|
-
"data": {
|
|
135
|
-
"headers": ["Username", "Email", "Status", "Staff", "Joined"],
|
|
136
|
-
"rows": [
|
|
137
|
-
[
|
|
138
|
-
user.username,
|
|
139
|
-
user.email,
|
|
140
|
-
"✅ Active" if user.is_active else "❌ Inactive",
|
|
141
|
-
"🛡️ Yes" if user.is_staff else "—",
|
|
142
|
-
user.date_joined.strftime('%Y-%m-%d %H:%M')
|
|
143
|
-
]
|
|
144
|
-
for user in recent_users
|
|
145
|
-
]
|
|
146
|
-
}
|
|
147
|
-
})
|
|
148
|
-
except Exception:
|
|
149
|
-
context.update({
|
|
150
|
-
"data": {
|
|
151
|
-
"headers": [],
|
|
152
|
-
"rows": []
|
|
153
|
-
}
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
return context
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
@register_component
|
|
160
|
-
class ChartsComponent(BaseComponent):
|
|
161
|
-
"""
|
|
162
|
-
Charts data component for analytics.
|
|
163
|
-
|
|
164
|
-
Provides chart data for user registrations and activity.
|
|
165
|
-
Supports time range filtering (7/30/90 days).
|
|
166
|
-
"""
|
|
167
|
-
|
|
168
|
-
def get_context_data(self, **kwargs):
|
|
169
|
-
"""Prepare charts data."""
|
|
170
|
-
context = super().get_context_data(**kwargs)
|
|
171
|
-
|
|
172
|
-
# Get time range from kwargs (default: 7 days)
|
|
173
|
-
days = kwargs.get('days', 7)
|
|
174
|
-
|
|
175
|
-
import json
|
|
176
|
-
from datetime import datetime, timedelta
|
|
177
|
-
|
|
178
|
-
from django.contrib.auth import get_user_model
|
|
179
|
-
from django.utils import timezone
|
|
180
|
-
|
|
181
|
-
User = get_user_model()
|
|
182
|
-
|
|
183
|
-
try:
|
|
184
|
-
# Get last N days
|
|
185
|
-
today = timezone.now().date()
|
|
186
|
-
dates = [(today - timedelta(days=i)).strftime('%Y-%m-%d')
|
|
187
|
-
for i in range(days-1, -1, -1)]
|
|
188
|
-
|
|
189
|
-
# User registrations
|
|
190
|
-
registrations = []
|
|
191
|
-
for date_str in dates:
|
|
192
|
-
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
193
|
-
count = User.objects.filter(date_joined__date=date_obj).count()
|
|
194
|
-
registrations.append(count)
|
|
195
|
-
|
|
196
|
-
# User activity (last login)
|
|
197
|
-
activity = []
|
|
198
|
-
for date_str in dates:
|
|
199
|
-
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
|
200
|
-
count = User.objects.filter(
|
|
201
|
-
last_login__date=date_obj
|
|
202
|
-
).count() if hasattr(User, 'last_login') else 0
|
|
203
|
-
activity.append(count)
|
|
204
|
-
|
|
205
|
-
# Format labels
|
|
206
|
-
day_labels = [d.split('-')[2] for d in dates]
|
|
207
|
-
|
|
208
|
-
# Chart data structures
|
|
209
|
-
user_reg_data = {
|
|
210
|
-
'labels': day_labels,
|
|
211
|
-
'datasets': [{
|
|
212
|
-
'label': 'New Users',
|
|
213
|
-
'data': registrations,
|
|
214
|
-
'backgroundColor': 'rgba(59, 130, 246, 0.5)',
|
|
215
|
-
'borderColor': 'rgb(59, 130, 246)',
|
|
216
|
-
'borderWidth': 2,
|
|
217
|
-
}]
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
user_activity_data = {
|
|
221
|
-
'labels': day_labels,
|
|
222
|
-
'datasets': [{
|
|
223
|
-
'label': 'Active Users',
|
|
224
|
-
'data': activity,
|
|
225
|
-
'backgroundColor': 'rgba(34, 197, 94, 0.5)',
|
|
226
|
-
'borderColor': 'rgb(34, 197, 94)',
|
|
227
|
-
'borderWidth': 2,
|
|
228
|
-
}]
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
context.update({
|
|
232
|
-
"data": {
|
|
233
|
-
"registrations": json.dumps(user_reg_data),
|
|
234
|
-
"activity": json.dumps(user_activity_data),
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
except Exception:
|
|
238
|
-
context.update({
|
|
239
|
-
"data": {}
|
|
240
|
-
})
|
|
241
|
-
|
|
242
|
-
return context
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
@register_component
|
|
246
|
-
class ActivityTrackerComponent(BaseComponent):
|
|
247
|
-
"""
|
|
248
|
-
Activity tracker component for GitHub-style heatmap.
|
|
249
|
-
|
|
250
|
-
Provides 365 days of user activity data with level indicators.
|
|
251
|
-
"""
|
|
252
|
-
|
|
253
|
-
def get_context_data(self, **kwargs):
|
|
254
|
-
"""Prepare activity tracker data."""
|
|
255
|
-
context = super().get_context_data(**kwargs)
|
|
256
|
-
|
|
257
|
-
from datetime import timedelta
|
|
258
|
-
|
|
259
|
-
from django.contrib.auth import get_user_model
|
|
260
|
-
from django.utils import timezone
|
|
261
|
-
|
|
262
|
-
User = get_user_model()
|
|
263
|
-
|
|
264
|
-
try:
|
|
265
|
-
today = timezone.now().date()
|
|
266
|
-
activity_data = []
|
|
267
|
-
|
|
268
|
-
for days_ago in range(364, -1, -1): # 365 days
|
|
269
|
-
date = today - timedelta(days=days_ago)
|
|
270
|
-
|
|
271
|
-
# Count registrations
|
|
272
|
-
registrations = User.objects.filter(
|
|
273
|
-
date_joined__date=date
|
|
274
|
-
).count()
|
|
275
|
-
|
|
276
|
-
# Count logins
|
|
277
|
-
logins = 0
|
|
278
|
-
if hasattr(User, 'last_login'):
|
|
279
|
-
logins = User.objects.filter(
|
|
280
|
-
last_login__date=date
|
|
281
|
-
).count()
|
|
282
|
-
|
|
283
|
-
total_activity = registrations + logins
|
|
284
|
-
|
|
285
|
-
activity_data.append({
|
|
286
|
-
'date': date.isoformat(),
|
|
287
|
-
'count': total_activity,
|
|
288
|
-
'level': self._get_activity_level(total_activity),
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
context.update({
|
|
292
|
-
"data": activity_data
|
|
293
|
-
})
|
|
294
|
-
except Exception:
|
|
295
|
-
context.update({
|
|
296
|
-
"data": []
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
return context
|
|
300
|
-
|
|
301
|
-
def _get_activity_level(self, count: int) -> int:
|
|
302
|
-
"""Convert activity count to level (0-4) for heatmap colors."""
|
|
303
|
-
if count == 0:
|
|
304
|
-
return 0
|
|
305
|
-
elif count <= 2:
|
|
306
|
-
return 1
|
|
307
|
-
elif count <= 5:
|
|
308
|
-
return 2
|
|
309
|
-
elif count <= 10:
|
|
310
|
-
return 3
|
|
311
|
-
else:
|
|
312
|
-
return 4
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Debug utilities for dashboard rendering.
|
|
3
|
-
|
|
4
|
-
Saves rendered HTML to disk for inspection and comparison.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
from datetime import datetime
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Any, Dict, Optional
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class DashboardDebugger:
|
|
14
|
-
"""Save dashboard renders for debugging."""
|
|
15
|
-
|
|
16
|
-
def __init__(self, output_dir: Optional[Path] = None):
|
|
17
|
-
"""
|
|
18
|
-
Initialize debugger.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
output_dir: Directory to save renders (default: django_cfg/debug/dashboard/)
|
|
22
|
-
"""
|
|
23
|
-
if output_dir is None:
|
|
24
|
-
# Use django_cfg package directory
|
|
25
|
-
django_cfg_root = Path(__file__).parent.parent
|
|
26
|
-
output_dir = django_cfg_root / 'debug' / 'dashboard'
|
|
27
|
-
|
|
28
|
-
self.output_dir = Path(output_dir)
|
|
29
|
-
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
-
|
|
31
|
-
def save_render(
|
|
32
|
-
self,
|
|
33
|
-
html: str,
|
|
34
|
-
name: str = 'dashboard',
|
|
35
|
-
context: Optional[Dict[str, Any]] = None,
|
|
36
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
37
|
-
) -> Path:
|
|
38
|
-
"""
|
|
39
|
-
Save rendered HTML with metadata.
|
|
40
|
-
|
|
41
|
-
Args:
|
|
42
|
-
html: Rendered HTML content
|
|
43
|
-
name: Base name for files
|
|
44
|
-
context: Template context data
|
|
45
|
-
metadata: Additional metadata
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
Path to saved HTML file
|
|
49
|
-
"""
|
|
50
|
-
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
51
|
-
base_name = f"{name}_{timestamp}"
|
|
52
|
-
|
|
53
|
-
# Save HTML
|
|
54
|
-
html_path = self.output_dir / f"{base_name}.html"
|
|
55
|
-
html_path.write_text(html, encoding='utf-8')
|
|
56
|
-
|
|
57
|
-
# Save context as JSON
|
|
58
|
-
if context:
|
|
59
|
-
context_path = self.output_dir / f"{base_name}_context.json"
|
|
60
|
-
# Convert context to JSON-serializable format
|
|
61
|
-
serializable_context = self._make_serializable(context)
|
|
62
|
-
context_path.write_text(
|
|
63
|
-
json.dumps(serializable_context, indent=2, ensure_ascii=False),
|
|
64
|
-
encoding='utf-8'
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
# Save metadata
|
|
68
|
-
meta = {
|
|
69
|
-
'timestamp': timestamp,
|
|
70
|
-
'name': name,
|
|
71
|
-
'html_size': len(html),
|
|
72
|
-
'html_lines': html.count('\n'),
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if metadata:
|
|
76
|
-
meta.update(metadata)
|
|
77
|
-
|
|
78
|
-
meta_path = self.output_dir / f"{base_name}_meta.json"
|
|
79
|
-
meta_path.write_text(
|
|
80
|
-
json.dumps(meta, indent=2, ensure_ascii=False),
|
|
81
|
-
encoding='utf-8'
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
print(f"✅ Saved dashboard render: {html_path}")
|
|
85
|
-
print(f" Context: {context_path if context else 'N/A'}")
|
|
86
|
-
print(f" Metadata: {meta_path}")
|
|
87
|
-
|
|
88
|
-
return html_path
|
|
89
|
-
|
|
90
|
-
def _make_serializable(self, obj: Any) -> Any:
|
|
91
|
-
"""Convert object to JSON-serializable format."""
|
|
92
|
-
if isinstance(obj, dict):
|
|
93
|
-
return {k: self._make_serializable(v) for k, v in obj.items()}
|
|
94
|
-
elif isinstance(obj, (list, tuple)):
|
|
95
|
-
return [self._make_serializable(item) for item in obj]
|
|
96
|
-
elif isinstance(obj, (str, int, float, bool, type(None))):
|
|
97
|
-
return obj
|
|
98
|
-
else:
|
|
99
|
-
# For non-serializable objects, use string representation
|
|
100
|
-
return str(obj)
|
|
101
|
-
|
|
102
|
-
def save_section_render(
|
|
103
|
-
self,
|
|
104
|
-
section_name: str,
|
|
105
|
-
html: str,
|
|
106
|
-
section_data: Optional[Dict[str, Any]] = None
|
|
107
|
-
) -> Path:
|
|
108
|
-
"""
|
|
109
|
-
Save individual section render.
|
|
110
|
-
|
|
111
|
-
Args:
|
|
112
|
-
section_name: Name of section (overview, stats, etc.)
|
|
113
|
-
html: Rendered HTML
|
|
114
|
-
section_data: Section-specific data
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
Path to saved file
|
|
118
|
-
"""
|
|
119
|
-
return self.save_render(
|
|
120
|
-
html=html,
|
|
121
|
-
name=f"section_{section_name}",
|
|
122
|
-
context=section_data,
|
|
123
|
-
metadata={'section': section_name}
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
def compare_with_archive(self, current_html: str, archive_path: Path) -> Dict[str, Any]:
|
|
127
|
-
"""
|
|
128
|
-
Compare current render with archived version.
|
|
129
|
-
|
|
130
|
-
Args:
|
|
131
|
-
current_html: Current rendered HTML
|
|
132
|
-
archive_path: Path to archived HTML
|
|
133
|
-
|
|
134
|
-
Returns:
|
|
135
|
-
Comparison results
|
|
136
|
-
"""
|
|
137
|
-
if not archive_path.exists():
|
|
138
|
-
return {
|
|
139
|
-
'error': f"Archive not found: {archive_path}"
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
archive_html = archive_path.read_text(encoding='utf-8')
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
'current_size': len(current_html),
|
|
146
|
-
'archive_size': len(archive_html),
|
|
147
|
-
'size_diff': len(current_html) - len(archive_html),
|
|
148
|
-
'current_lines': current_html.count('\n'),
|
|
149
|
-
'archive_lines': archive_html.count('\n'),
|
|
150
|
-
'lines_diff': current_html.count('\n') - archive_html.count('\n'),
|
|
151
|
-
'identical': current_html == archive_html,
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
# Global instance
|
|
156
|
-
_debugger: Optional[DashboardDebugger] = None
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def get_debugger() -> DashboardDebugger:
|
|
160
|
-
"""Get or create global debugger instance."""
|
|
161
|
-
global _debugger
|
|
162
|
-
if _debugger is None:
|
|
163
|
-
_debugger = DashboardDebugger()
|
|
164
|
-
return _debugger
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def save_dashboard_render(html: str, **kwargs) -> Path:
|
|
168
|
-
"""Convenience function to save dashboard render."""
|
|
169
|
-
return get_debugger().save_render(html, **kwargs)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
def save_section_render(section_name: str, html: str, **kwargs) -> Path:
|
|
173
|
-
"""Convenience function to save section render."""
|
|
174
|
-
return get_debugger().save_section_render(section_name, html, **kwargs)
|
|
File without changes
|
|
File without changes
|