django-cfg 1.4.87__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/permissions.py +48 -0
- django_cfg/apps/dashboard/serializers/__init__.py +8 -1
- django_cfg/apps/dashboard/serializers/commands.py +29 -0
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/{modules/django_unfold/callbacks/base.py → apps/dashboard/services/commands_security.py} +28 -90
- django_cfg/apps/dashboard/services/commands_service.py +208 -9
- django_cfg/apps/dashboard/services/overview_service.py +205 -0
- django_cfg/apps/dashboard/views/commands_views.py +92 -4
- django_cfg/apps/frontend/test_routing.py +134 -0
- django_cfg/apps/frontend/views.py +73 -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/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{19430.fe7bff7372f8a256.js → 19430.c4c95603c23c17fe.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/94141-bc6d47f419b26b21.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-c336f254967dd101.js → _app-c7dcd3aa616fab68.js} +6 -6
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-b39c7f22c066e2c6.js → cookies-97d279800f12aab4.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-5aedad0cf3a4f80f.js → privacy-1d5e6cd94689247e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-dbd854d0d5d483e2.js → security-55e49700e7a01f5a.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-f3e1d2b9e5edf12f.js → terms-14c02bb2d3198352.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-22532c65971225eb.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-d4ccbe1265cbd853.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{webpack-da114020a6b940f5.js → webpack-5a92f81363b62aa7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/css/3063068f0d5a8a00.css +3 -0
- django_cfg/static/frontend/admin/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/21350d82a1f187e9-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- 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/templates/admin/index.html +1 -1
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/RECORD +62 -163
- 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/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/23004-faae121bbfecc163.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-48bd5701f62faf27.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +0 -1
- django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +0 -3
- django_cfg/static/frontend/admin/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/875ae681bfde4580-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
- 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/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
"""Widgets section for dashboard.
|
|
2
|
-
|
|
3
|
-
Automatically renders widgets from DashboardManager.get_widgets_config()
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
from datetime import timedelta
|
|
7
|
-
from typing import Any, Dict, List
|
|
8
|
-
|
|
9
|
-
import psutil
|
|
10
|
-
from django.db.models import Avg
|
|
11
|
-
from django.template import Context, Template
|
|
12
|
-
from django.utils import timezone
|
|
13
|
-
|
|
14
|
-
from .base import DataSection
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class WidgetsSection(DataSection):
|
|
18
|
-
"""
|
|
19
|
-
Widgets section showing automatically generated dashboard widgets.
|
|
20
|
-
|
|
21
|
-
Widgets are defined in DashboardManager.get_widgets_config() and
|
|
22
|
-
can include:
|
|
23
|
-
- System metrics (CPU, Memory, Disk)
|
|
24
|
-
- RPC monitoring stats
|
|
25
|
-
- Custom application widgets
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
template_name = "admin/sections/widgets_section.html"
|
|
29
|
-
title = "Dashboard Widgets"
|
|
30
|
-
icon = "widgets"
|
|
31
|
-
|
|
32
|
-
def get_data(self) -> Dict[str, Any]:
|
|
33
|
-
"""Get widgets configuration from DashboardManager."""
|
|
34
|
-
from django_cfg.modules.django_unfold.dashboard import get_dashboard_manager
|
|
35
|
-
|
|
36
|
-
dashboard_manager = get_dashboard_manager()
|
|
37
|
-
|
|
38
|
-
# Get widgets from dashboard manager (base system widgets)
|
|
39
|
-
widgets = dashboard_manager.get_widgets_config()
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
'widgets': widgets,
|
|
43
|
-
'widgets_count': len(widgets),
|
|
44
|
-
'has_widgets': len(widgets) > 0,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
def merge_custom_widgets(self, widgets: List[Dict], custom_widgets: List[Any]) -> List[Dict]:
|
|
48
|
-
"""
|
|
49
|
-
Merge custom widgets from dashboard_callback.
|
|
50
|
-
|
|
51
|
-
Allows projects to add widgets via dashboard_callback:
|
|
52
|
-
context["custom_widgets"] = [
|
|
53
|
-
StatsCardsWidget(...),
|
|
54
|
-
...
|
|
55
|
-
]
|
|
56
|
-
"""
|
|
57
|
-
if not custom_widgets:
|
|
58
|
-
return widgets
|
|
59
|
-
|
|
60
|
-
# Convert custom widgets to dicts if they are Pydantic models
|
|
61
|
-
for widget in custom_widgets:
|
|
62
|
-
if hasattr(widget, 'to_dict'):
|
|
63
|
-
widgets.append(widget.to_dict())
|
|
64
|
-
elif hasattr(widget, 'model_dump'):
|
|
65
|
-
widgets.append(widget.model_dump())
|
|
66
|
-
elif isinstance(widget, dict):
|
|
67
|
-
widgets.append(widget)
|
|
68
|
-
|
|
69
|
-
return widgets
|
|
70
|
-
|
|
71
|
-
def get_context_data(self, **kwargs) -> Dict[str, Any]:
|
|
72
|
-
"""Add additional context for widget rendering."""
|
|
73
|
-
context = super().get_context_data(**kwargs)
|
|
74
|
-
|
|
75
|
-
# Get base widgets from DashboardManager
|
|
76
|
-
widgets = context['data']['widgets']
|
|
77
|
-
|
|
78
|
-
# Merge custom widgets from dashboard_callback if provided
|
|
79
|
-
custom_widgets_from_callback = kwargs.get('custom_widgets', [])
|
|
80
|
-
if custom_widgets_from_callback:
|
|
81
|
-
widgets = self.merge_custom_widgets(widgets, custom_widgets_from_callback)
|
|
82
|
-
# Update count
|
|
83
|
-
context['data']['widgets_count'] = len(widgets)
|
|
84
|
-
context['data']['has_widgets'] = len(widgets) > 0
|
|
85
|
-
|
|
86
|
-
# Get metrics data first
|
|
87
|
-
metrics_data = {}
|
|
88
|
-
metrics_data.update(self.get_system_metrics())
|
|
89
|
-
|
|
90
|
-
# Add Centrifugo metrics if enabled
|
|
91
|
-
dashboard_manager = self._get_dashboard_manager()
|
|
92
|
-
if dashboard_manager.is_centrifugo_enabled():
|
|
93
|
-
metrics_data.update(self.get_centrifugo_metrics())
|
|
94
|
-
|
|
95
|
-
# Also merge any custom metrics from kwargs
|
|
96
|
-
custom_metrics = kwargs.get('custom_metrics', {})
|
|
97
|
-
if custom_metrics:
|
|
98
|
-
metrics_data.update(custom_metrics)
|
|
99
|
-
|
|
100
|
-
# Process widgets and resolve template variables
|
|
101
|
-
processed_stats_widgets = []
|
|
102
|
-
for widget in widgets:
|
|
103
|
-
if widget.get('type') == 'stats_cards':
|
|
104
|
-
processed_widget = self._process_stats_widget(widget, metrics_data)
|
|
105
|
-
processed_stats_widgets.append(processed_widget)
|
|
106
|
-
|
|
107
|
-
chart_widgets = [w for w in widgets if w.get('type') == 'chart']
|
|
108
|
-
custom_widgets = [w for w in widgets if w.get('type') not in ['stats_cards', 'chart']]
|
|
109
|
-
|
|
110
|
-
# Add processed widgets
|
|
111
|
-
context.update({
|
|
112
|
-
'stats_widgets': processed_stats_widgets,
|
|
113
|
-
'chart_widgets': chart_widgets,
|
|
114
|
-
'custom_widgets': custom_widgets,
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
# Also add metrics for direct access
|
|
118
|
-
context.update(metrics_data)
|
|
119
|
-
|
|
120
|
-
return context
|
|
121
|
-
|
|
122
|
-
def _get_dashboard_manager(self):
|
|
123
|
-
"""Get dashboard manager instance (lazy import to avoid circular dependencies)."""
|
|
124
|
-
from django_cfg.modules.django_unfold.dashboard import get_dashboard_manager
|
|
125
|
-
return get_dashboard_manager()
|
|
126
|
-
|
|
127
|
-
def _process_stats_widget(self, widget: Dict[str, Any], context_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
128
|
-
"""Process StatsCardsWidget and resolve template variables in cards."""
|
|
129
|
-
processed_widget = widget.copy()
|
|
130
|
-
processed_cards = []
|
|
131
|
-
|
|
132
|
-
for card in widget.get('cards', []):
|
|
133
|
-
processed_card = card.copy()
|
|
134
|
-
|
|
135
|
-
# Resolve value_template using Django template engine
|
|
136
|
-
value_template = card.get('value_template', '')
|
|
137
|
-
if '{{' in value_template:
|
|
138
|
-
try:
|
|
139
|
-
template = Template(value_template)
|
|
140
|
-
context = Context(context_data)
|
|
141
|
-
resolved_value = template.render(context)
|
|
142
|
-
processed_card['value_template'] = resolved_value
|
|
143
|
-
except Exception:
|
|
144
|
-
# Keep original if rendering fails
|
|
145
|
-
pass
|
|
146
|
-
|
|
147
|
-
# Also resolve change field if it has template variables
|
|
148
|
-
change_template = card.get('change', '')
|
|
149
|
-
if change_template and '{{' in change_template:
|
|
150
|
-
try:
|
|
151
|
-
template = Template(change_template)
|
|
152
|
-
context = Context(context_data)
|
|
153
|
-
resolved_change = template.render(context)
|
|
154
|
-
processed_card['change'] = resolved_change
|
|
155
|
-
except Exception:
|
|
156
|
-
# Keep original if rendering fails
|
|
157
|
-
pass
|
|
158
|
-
|
|
159
|
-
processed_cards.append(processed_card)
|
|
160
|
-
|
|
161
|
-
processed_widget['cards'] = processed_cards
|
|
162
|
-
return processed_widget
|
|
163
|
-
|
|
164
|
-
def get_system_metrics(self) -> Dict[str, Any]:
|
|
165
|
-
"""Get system metrics for widgets."""
|
|
166
|
-
return {
|
|
167
|
-
'cpu_percent': round(psutil.cpu_percent(interval=0.1), 1),
|
|
168
|
-
'memory_percent': round(psutil.virtual_memory().percent, 1),
|
|
169
|
-
'disk_percent': round(psutil.disk_usage('/').percent, 1),
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
def get_centrifugo_metrics(self) -> Dict[str, Any]:
|
|
173
|
-
"""Get Centrifugo metrics for widgets."""
|
|
174
|
-
try:
|
|
175
|
-
from django_cfg.apps.centrifugo.models import CentrifugoLog
|
|
176
|
-
|
|
177
|
-
# Get stats for last 24 hours
|
|
178
|
-
since = timezone.now() - timedelta(hours=24)
|
|
179
|
-
|
|
180
|
-
logs = CentrifugoLog.objects.filter(created_at__gte=since)
|
|
181
|
-
|
|
182
|
-
total_publishes = logs.count()
|
|
183
|
-
successful_publishes = logs.filter(status='success').count()
|
|
184
|
-
failed_publishes = logs.filter(status='failed').count()
|
|
185
|
-
timeout_publishes = logs.filter(status='timeout').count()
|
|
186
|
-
|
|
187
|
-
success_rate = round((successful_publishes / total_publishes * 100) if total_publishes > 0 else 0, 1)
|
|
188
|
-
|
|
189
|
-
avg_duration = logs.filter(
|
|
190
|
-
duration_ms__isnull=False
|
|
191
|
-
).aggregate(
|
|
192
|
-
avg=Avg('duration_ms')
|
|
193
|
-
)['avg']
|
|
194
|
-
|
|
195
|
-
avg_duration = round(avg_duration, 1) if avg_duration else 0 # Already in ms
|
|
196
|
-
|
|
197
|
-
avg_acks = logs.filter(
|
|
198
|
-
acks_received__isnull=False
|
|
199
|
-
).aggregate(
|
|
200
|
-
avg=Avg('acks_received')
|
|
201
|
-
)['avg']
|
|
202
|
-
|
|
203
|
-
avg_acks = round(avg_acks, 1) if avg_acks else 0
|
|
204
|
-
|
|
205
|
-
return {
|
|
206
|
-
'centrifugo_total_publishes': total_publishes,
|
|
207
|
-
'centrifugo_success_rate': success_rate,
|
|
208
|
-
'centrifugo_avg_duration': avg_duration,
|
|
209
|
-
'centrifugo_failed_publishes': failed_publishes,
|
|
210
|
-
'centrifugo_timeout_publishes': timeout_publishes,
|
|
211
|
-
'centrifugo_avg_acks': avg_acks,
|
|
212
|
-
}
|
|
213
|
-
except Exception as e:
|
|
214
|
-
# Return zeros if Centrifugo models not available
|
|
215
|
-
return {
|
|
216
|
-
'centrifugo_total_publishes': 0,
|
|
217
|
-
'centrifugo_success_rate': 0,
|
|
218
|
-
'centrifugo_avg_duration': 0,
|
|
219
|
-
'centrifugo_failed_publishes': 0,
|
|
220
|
-
'centrifugo_timeout_publishes': 0,
|
|
221
|
-
'centrifugo_avg_acks': 0,
|
|
222
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Quick actions callbacks.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from typing import List
|
|
7
|
-
|
|
8
|
-
from django_cfg.modules.django_admin.icons import Icons
|
|
9
|
-
|
|
10
|
-
from ..models.dashboard import QuickAction
|
|
11
|
-
from .base import get_user_admin_urls
|
|
12
|
-
|
|
13
|
-
logger = logging.getLogger(__name__)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class ActionsCallbacks:
|
|
17
|
-
"""Quick actions callbacks."""
|
|
18
|
-
|
|
19
|
-
def get_quick_actions(self) -> List[QuickAction]:
|
|
20
|
-
"""Get quick action buttons as Pydantic models."""
|
|
21
|
-
# Get user admin URLs dynamically based on AUTH_USER_MODEL
|
|
22
|
-
user_admin_urls = get_user_admin_urls()
|
|
23
|
-
|
|
24
|
-
actions = [
|
|
25
|
-
QuickAction(
|
|
26
|
-
title="Add User",
|
|
27
|
-
description="Create new user account",
|
|
28
|
-
icon=Icons.PERSON_ADD,
|
|
29
|
-
link=user_admin_urls["add"],
|
|
30
|
-
color="primary",
|
|
31
|
-
category="admin",
|
|
32
|
-
),
|
|
33
|
-
QuickAction(
|
|
34
|
-
title="Support Tickets",
|
|
35
|
-
description="Manage support tickets",
|
|
36
|
-
icon=Icons.SUPPORT_AGENT,
|
|
37
|
-
link="admin:django_cfg_support_ticket_changelist",
|
|
38
|
-
color="primary",
|
|
39
|
-
category="support",
|
|
40
|
-
),
|
|
41
|
-
QuickAction(
|
|
42
|
-
title="Health Check",
|
|
43
|
-
description="System health status",
|
|
44
|
-
icon=Icons.HEALTH_AND_SAFETY,
|
|
45
|
-
link="/cfg/health/",
|
|
46
|
-
color="success",
|
|
47
|
-
category="system",
|
|
48
|
-
),
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
return actions
|
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django Client (OpenAPI) integration callbacks.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from typing import Any, Dict, List, Tuple
|
|
7
|
-
|
|
8
|
-
from django.conf import settings
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class OpenAPIClientCallbacks:
|
|
14
|
-
"""Django Client (OpenAPI) integration callbacks."""
|
|
15
|
-
|
|
16
|
-
def get_openapi_groups_data(self) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
|
|
17
|
-
"""Get Django Client (OpenAPI) groups data."""
|
|
18
|
-
try:
|
|
19
|
-
# Get groups from OpenAPI service (includes default cfg group)
|
|
20
|
-
from django_cfg.modules.django_client.core import get_openapi_service
|
|
21
|
-
|
|
22
|
-
service = get_openapi_service()
|
|
23
|
-
if not service.config:
|
|
24
|
-
return [], {"total_apps": 0, "total_endpoints": 0, "total_groups": 0}
|
|
25
|
-
|
|
26
|
-
# Get groups with defaults (includes cfg group automatically)
|
|
27
|
-
groups_dict = service.get_groups()
|
|
28
|
-
groups_list = list(groups_dict.values())
|
|
29
|
-
api_prefix = getattr(service.config, "api_prefix", "api")
|
|
30
|
-
|
|
31
|
-
# Ensure urlconf modules AND URL patterns are created for all groups with apps
|
|
32
|
-
try:
|
|
33
|
-
from django.urls import path
|
|
34
|
-
from drf_spectacular.views import SpectacularAPIView
|
|
35
|
-
from django_cfg.modules.django_client.core.groups import GroupManager
|
|
36
|
-
from django_cfg.modules.django_client import urls as client_urls
|
|
37
|
-
|
|
38
|
-
manager = GroupManager(service.config)
|
|
39
|
-
for group in groups_list:
|
|
40
|
-
group_name = getattr(group, "name", "unknown") if not isinstance(group, dict) else group.get("name", "unknown")
|
|
41
|
-
apps = getattr(group, "apps", []) if not isinstance(group, dict) else group.get("apps", [])
|
|
42
|
-
group_version = getattr(group, "version", "1.0.0") if not isinstance(group, dict) else group.get("version", "1.0.0")
|
|
43
|
-
|
|
44
|
-
if apps:
|
|
45
|
-
try:
|
|
46
|
-
# Create urlconf module
|
|
47
|
-
manager.create_urlconf_module(group_name)
|
|
48
|
-
|
|
49
|
-
# Check if URL pattern already exists
|
|
50
|
-
url_name = f'openapi-schema-{group_name}'
|
|
51
|
-
url_exists = any(
|
|
52
|
-
hasattr(pattern, 'name') and pattern.name == url_name
|
|
53
|
-
for pattern in client_urls.urlpatterns
|
|
54
|
-
)
|
|
55
|
-
|
|
56
|
-
# Add URL pattern if it doesn't exist
|
|
57
|
-
if not url_exists:
|
|
58
|
-
new_pattern = path(
|
|
59
|
-
f'{group_name}/schema/',
|
|
60
|
-
SpectacularAPIView.as_view(
|
|
61
|
-
urlconf=f'_django_client_urlconf_{group_name}',
|
|
62
|
-
api_version=group_version,
|
|
63
|
-
),
|
|
64
|
-
name=url_name,
|
|
65
|
-
)
|
|
66
|
-
client_urls.urlpatterns.append(new_pattern)
|
|
67
|
-
except Exception:
|
|
68
|
-
pass # Silently skip if already exists or fails
|
|
69
|
-
except Exception:
|
|
70
|
-
pass # Silently skip if GroupManager fails
|
|
71
|
-
|
|
72
|
-
groups_data = []
|
|
73
|
-
total_apps = 0
|
|
74
|
-
total_endpoints = 0
|
|
75
|
-
|
|
76
|
-
for group in groups_list:
|
|
77
|
-
# Handle both dict and object access
|
|
78
|
-
if isinstance(group, dict):
|
|
79
|
-
group_name = group.get("name", "unknown")
|
|
80
|
-
title = group.get("title", group_name.title())
|
|
81
|
-
description = group.get("description", f"{group_name} group")
|
|
82
|
-
apps = group.get("apps", [])
|
|
83
|
-
else:
|
|
84
|
-
# Handle object access (for OpenAPIGroupConfig instances)
|
|
85
|
-
group_name = getattr(group, "name", "unknown")
|
|
86
|
-
title = getattr(group, "title", group_name.title())
|
|
87
|
-
description = getattr(group, "description", f"{group_name} group")
|
|
88
|
-
apps = getattr(group, "apps", [])
|
|
89
|
-
|
|
90
|
-
# Count actual endpoints by checking URL patterns (simplified estimate)
|
|
91
|
-
endpoint_count = len(apps) * 3 # Conservative estimate
|
|
92
|
-
|
|
93
|
-
groups_data.append({
|
|
94
|
-
"name": group_name,
|
|
95
|
-
"title": title,
|
|
96
|
-
"description": description,
|
|
97
|
-
"app_count": len(apps),
|
|
98
|
-
"endpoint_count": endpoint_count,
|
|
99
|
-
"status": "active",
|
|
100
|
-
"schema_url": f"/cfg/openapi/{group_name}/schema/",
|
|
101
|
-
"api_url": f"/{api_prefix}/{group_name}/",
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
total_apps += len(apps)
|
|
105
|
-
total_endpoints += endpoint_count
|
|
106
|
-
|
|
107
|
-
return groups_data, {
|
|
108
|
-
"total_apps": total_apps,
|
|
109
|
-
"total_endpoints": total_endpoints,
|
|
110
|
-
"total_groups": len(groups_list),
|
|
111
|
-
}
|
|
112
|
-
except Exception as e:
|
|
113
|
-
logger.error(f"Error getting OpenAPI groups: {e}")
|
|
114
|
-
return [], {
|
|
115
|
-
"total_apps": 0,
|
|
116
|
-
"total_endpoints": 0,
|
|
117
|
-
"total_groups": 0,
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Keep backward compatibility alias
|
|
122
|
-
RevolutionCallbacks = OpenAPIClientCallbacks
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Charts data callbacks.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from datetime import timedelta
|
|
7
|
-
from typing import Any, Dict, List
|
|
8
|
-
|
|
9
|
-
from django.contrib.auth import get_user_model
|
|
10
|
-
from django.db.models import Count
|
|
11
|
-
from django.utils import timezone
|
|
12
|
-
|
|
13
|
-
from ..models.dashboard import ChartData, ChartDataset
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class ChartsCallbacks:
|
|
19
|
-
"""Charts data callbacks."""
|
|
20
|
-
|
|
21
|
-
def _get_user_model(self):
|
|
22
|
-
"""Get the user model safely."""
|
|
23
|
-
return get_user_model()
|
|
24
|
-
|
|
25
|
-
def _get_empty_chart_data(self, label: str) -> Dict[str, Any]:
|
|
26
|
-
"""Get empty chart data structure."""
|
|
27
|
-
return ChartData(
|
|
28
|
-
labels=["No Data"],
|
|
29
|
-
datasets=[
|
|
30
|
-
ChartDataset(
|
|
31
|
-
label=label,
|
|
32
|
-
data=[0],
|
|
33
|
-
backgroundColor="rgba(156, 163, 175, 0.1)",
|
|
34
|
-
borderColor="rgb(156, 163, 175)",
|
|
35
|
-
tension=0.4
|
|
36
|
-
)
|
|
37
|
-
]
|
|
38
|
-
).model_dump()
|
|
39
|
-
|
|
40
|
-
def get_user_registration_chart_data(self) -> Dict[str, Any]:
|
|
41
|
-
"""Get user registration chart data."""
|
|
42
|
-
try:
|
|
43
|
-
# Avoid database access during app initialization
|
|
44
|
-
from django.apps import apps
|
|
45
|
-
if not apps.ready:
|
|
46
|
-
return self._get_empty_chart_data("New Users")
|
|
47
|
-
|
|
48
|
-
User = self._get_user_model()
|
|
49
|
-
|
|
50
|
-
# Get last 7 days of registration data
|
|
51
|
-
end_date = timezone.now().date()
|
|
52
|
-
start_date = end_date - timedelta(days=6)
|
|
53
|
-
|
|
54
|
-
# Generate date range
|
|
55
|
-
date_range = []
|
|
56
|
-
current_date = start_date
|
|
57
|
-
while current_date <= end_date:
|
|
58
|
-
date_range.append(current_date)
|
|
59
|
-
current_date += timedelta(days=1)
|
|
60
|
-
|
|
61
|
-
# Get registration counts by date
|
|
62
|
-
registration_data = (
|
|
63
|
-
User.objects.filter(date_joined__date__gte=start_date)
|
|
64
|
-
.extra({'date': "date(date_joined)"})
|
|
65
|
-
.values('date')
|
|
66
|
-
.annotate(count=Count('id'))
|
|
67
|
-
.order_by('date')
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Create data dictionary for easy lookup
|
|
71
|
-
data_dict = {item['date']: item['count'] for item in registration_data}
|
|
72
|
-
|
|
73
|
-
# Build chart data
|
|
74
|
-
labels = [date.strftime("%m/%d") for date in date_range]
|
|
75
|
-
data_points = [data_dict.get(date, 0) for date in date_range]
|
|
76
|
-
|
|
77
|
-
chart_data = ChartData(
|
|
78
|
-
labels=labels,
|
|
79
|
-
datasets=[
|
|
80
|
-
ChartDataset(
|
|
81
|
-
label="New Users",
|
|
82
|
-
data=data_points,
|
|
83
|
-
backgroundColor="rgba(59, 130, 246, 0.1)",
|
|
84
|
-
borderColor="rgb(59, 130, 246)",
|
|
85
|
-
tension=0.4
|
|
86
|
-
)
|
|
87
|
-
]
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
return chart_data.model_dump()
|
|
91
|
-
|
|
92
|
-
except Exception as e:
|
|
93
|
-
logger.error(f"Error getting user registration chart data: {e}")
|
|
94
|
-
return self._get_empty_chart_data("New Users")
|
|
95
|
-
|
|
96
|
-
def get_user_activity_chart_data(self) -> Dict[str, Any]:
|
|
97
|
-
"""Get user activity chart data."""
|
|
98
|
-
try:
|
|
99
|
-
# Avoid database access during app initialization
|
|
100
|
-
from django.apps import apps
|
|
101
|
-
if not apps.ready:
|
|
102
|
-
return self._get_empty_chart_data("Active Users")
|
|
103
|
-
|
|
104
|
-
User = self._get_user_model()
|
|
105
|
-
|
|
106
|
-
# Get activity data for last 7 days
|
|
107
|
-
end_date = timezone.now().date()
|
|
108
|
-
start_date = end_date - timedelta(days=6)
|
|
109
|
-
|
|
110
|
-
# Generate date range
|
|
111
|
-
date_range = []
|
|
112
|
-
current_date = start_date
|
|
113
|
-
while current_date <= end_date:
|
|
114
|
-
date_range.append(current_date)
|
|
115
|
-
current_date += timedelta(days=1)
|
|
116
|
-
|
|
117
|
-
# Get login activity (users who logged in each day)
|
|
118
|
-
activity_data = (
|
|
119
|
-
User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
|
|
120
|
-
.extra({'date': "date(last_login)"})
|
|
121
|
-
.values('date')
|
|
122
|
-
.annotate(count=Count('id'))
|
|
123
|
-
.order_by('date')
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
# Create data dictionary for easy lookup
|
|
127
|
-
data_dict = {item['date']: item['count'] for item in activity_data}
|
|
128
|
-
|
|
129
|
-
# Build chart data
|
|
130
|
-
labels = [date.strftime("%m/%d") for date in date_range]
|
|
131
|
-
data_points = [data_dict.get(date, 0) for date in date_range]
|
|
132
|
-
|
|
133
|
-
chart_data = ChartData(
|
|
134
|
-
labels=labels,
|
|
135
|
-
datasets=[
|
|
136
|
-
ChartDataset(
|
|
137
|
-
label="Active Users",
|
|
138
|
-
data=data_points,
|
|
139
|
-
backgroundColor="rgba(34, 197, 94, 0.1)",
|
|
140
|
-
borderColor="rgb(34, 197, 94)",
|
|
141
|
-
tension=0.4
|
|
142
|
-
)
|
|
143
|
-
]
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
return chart_data.model_dump()
|
|
147
|
-
|
|
148
|
-
except Exception as e:
|
|
149
|
-
logger.error(f"Error getting user activity chart data: {e}")
|
|
150
|
-
return self._get_empty_chart_data("Active Users")
|
|
151
|
-
|
|
152
|
-
def get_activity_tracker_data(self) -> List[Dict[str, str]]:
|
|
153
|
-
"""Get activity tracker data for the last 52 weeks (GitHub-style)."""
|
|
154
|
-
try:
|
|
155
|
-
# Avoid database access during app initialization
|
|
156
|
-
from django.apps import apps
|
|
157
|
-
if not apps.ready:
|
|
158
|
-
return self._get_empty_tracker_data()
|
|
159
|
-
|
|
160
|
-
User = self._get_user_model()
|
|
161
|
-
|
|
162
|
-
# Get data for last 52 weeks (365 days)
|
|
163
|
-
end_date = timezone.now().date()
|
|
164
|
-
start_date = end_date - timedelta(days=364) # 52 weeks * 7 days - 1
|
|
165
|
-
|
|
166
|
-
# Get activity data by date
|
|
167
|
-
activity_data = (
|
|
168
|
-
User.objects.filter(last_login__date__gte=start_date, last_login__isnull=False)
|
|
169
|
-
.extra({'date': "date(last_login)"})
|
|
170
|
-
.values('date')
|
|
171
|
-
.annotate(count=Count('id'))
|
|
172
|
-
.order_by('date')
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
# Create data dictionary for easy lookup
|
|
176
|
-
data_dict = {item['date']: item['count'] for item in activity_data}
|
|
177
|
-
|
|
178
|
-
# Generate tracker data for each day
|
|
179
|
-
tracker_data = []
|
|
180
|
-
current_date = start_date
|
|
181
|
-
|
|
182
|
-
while current_date <= end_date:
|
|
183
|
-
activity_count = data_dict.get(current_date, 0)
|
|
184
|
-
|
|
185
|
-
# Determine color based on activity level
|
|
186
|
-
if activity_count == 0:
|
|
187
|
-
color = "bg-base-200 dark:bg-base-700"
|
|
188
|
-
level = "No activity"
|
|
189
|
-
elif activity_count <= 2:
|
|
190
|
-
color = "bg-green-200 dark:bg-green-800"
|
|
191
|
-
level = "Low activity"
|
|
192
|
-
elif activity_count <= 5:
|
|
193
|
-
color = "bg-green-400 dark:bg-green-600"
|
|
194
|
-
level = "Medium activity"
|
|
195
|
-
elif activity_count <= 10:
|
|
196
|
-
color = "bg-green-600 dark:bg-green-500"
|
|
197
|
-
level = "High activity"
|
|
198
|
-
else:
|
|
199
|
-
color = "bg-green-800 dark:bg-green-400"
|
|
200
|
-
level = "Very high activity"
|
|
201
|
-
|
|
202
|
-
tracker_data.append({
|
|
203
|
-
"color": color,
|
|
204
|
-
"tooltip": f"{current_date.strftime('%Y-%m-%d')}: {activity_count} active users ({level})"
|
|
205
|
-
})
|
|
206
|
-
|
|
207
|
-
current_date += timedelta(days=1)
|
|
208
|
-
|
|
209
|
-
return tracker_data
|
|
210
|
-
|
|
211
|
-
except Exception as e:
|
|
212
|
-
logger.error(f"Error getting activity tracker data: {e}")
|
|
213
|
-
return self._get_empty_tracker_data()
|
|
214
|
-
|
|
215
|
-
def _get_empty_tracker_data(self) -> List[Dict[str, str]]:
|
|
216
|
-
"""Get empty tracker data (365 days of no activity)."""
|
|
217
|
-
tracker_data = []
|
|
218
|
-
for i in range(365):
|
|
219
|
-
tracker_data.append({
|
|
220
|
-
"color": "bg-base-200 dark:bg-base-700",
|
|
221
|
-
"tooltip": f"Day {i + 1}: No data available"
|
|
222
|
-
})
|
|
223
|
-
return tracker_data
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Django commands callbacks.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import logging
|
|
6
|
-
from typing import Any, Dict
|
|
7
|
-
|
|
8
|
-
from .base import get_available_commands, get_commands_by_category
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class CommandsCallbacks:
|
|
14
|
-
"""Django commands callbacks."""
|
|
15
|
-
|
|
16
|
-
def get_django_commands(self) -> Dict[str, Any]:
|
|
17
|
-
"""Get Django management commands information."""
|
|
18
|
-
try:
|
|
19
|
-
commands = get_available_commands()
|
|
20
|
-
categorized = get_commands_by_category()
|
|
21
|
-
|
|
22
|
-
return {
|
|
23
|
-
"commands": commands,
|
|
24
|
-
"categorized": categorized,
|
|
25
|
-
"total_commands": len(commands),
|
|
26
|
-
"categories": list(categorized.keys()),
|
|
27
|
-
"core_commands": len([cmd for cmd in commands if cmd['is_core']]),
|
|
28
|
-
"custom_commands": len([cmd for cmd in commands if cmd['is_custom']]),
|
|
29
|
-
}
|
|
30
|
-
except Exception as e:
|
|
31
|
-
logger.error(f"Error getting Django commands: {e}")
|
|
32
|
-
# Return safe fallback to prevent dashboard from breaking
|
|
33
|
-
return {
|
|
34
|
-
"commands": [],
|
|
35
|
-
"categorized": {},
|
|
36
|
-
"total_commands": 0,
|
|
37
|
-
"categories": [],
|
|
38
|
-
"core_commands": 0,
|
|
39
|
-
"custom_commands": 0,
|
|
40
|
-
}
|