django-cfg 1.4.75__py3-none-any.whl → 1.4.76__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/agents/__init__.py +1 -1
- django_cfg/apps/agents/integration/registry.py +1 -1
- django_cfg/apps/agents/patterns/content_agents.py +1 -1
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +1 -1
- django_cfg/apps/centrifugo/views/dashboard.py +13 -0
- django_cfg/apps/centrifugo/views/testing_api.py +74 -15
- django_cfg/apps/tasks/views/dashboard.py +4 -4
- django_cfg/core/generation/integration_generators/api.py +5 -2
- django_cfg/management/commands/check_endpoints.py +1 -1
- django_cfg/modules/django_tailwind/templates/django_tailwind/components/navbar.html +2 -2
- django_cfg/modules/django_unfold/callbacks/main.py +27 -25
- django_cfg/pyproject.toml +1 -1
- django_cfg/static/admin/css/constance.css +44 -0
- django_cfg/static/admin/css/dashboard.css +6 -170
- django_cfg/static/admin/css/layout.css +21 -0
- django_cfg/static/admin/css/tabs.css +95 -0
- django_cfg/static/admin/css/theme.css +74 -0
- django_cfg/static/admin/js/alpine/activity-tracker.js +55 -0
- django_cfg/static/admin/js/alpine/chart.js +101 -0
- django_cfg/static/admin/js/alpine/command-modal.js +159 -0
- django_cfg/static/admin/js/alpine/commands-panel.js +139 -0
- django_cfg/static/admin/js/alpine/commands-section.js +260 -0
- django_cfg/static/admin/js/alpine/dashboard-tabs.js +46 -0
- django_cfg/static/admin/js/alpine/system-metrics.js +20 -0
- django_cfg/static/admin/js/alpine/toggle-section.js +28 -0
- django_cfg/static/admin/js/utils.js +60 -0
- django_cfg/templates/admin/components/modal.html +1 -1
- django_cfg/templates/admin/constance/change_list.html +3 -42
- django_cfg/templates/admin/index.html +0 -8
- django_cfg/templates/admin/layouts/base_dashboard.html +4 -2
- django_cfg/templates/admin/layouts/dashboard_with_tabs.html +104 -502
- django_cfg/templates/admin/sections/commands_section.html +374 -451
- django_cfg/templates/admin/sections/documentation_section.html +13 -33
- django_cfg/templates/admin/snippets/components/activity_tracker.html +27 -49
- django_cfg/templates/admin/snippets/components/charts_section.html +8 -74
- django_cfg/templates/admin/snippets/components/django_commands.html +94 -181
- django_cfg/templates/admin/snippets/components/system_metrics.html +18 -10
- django_cfg/templates/admin/snippets/tabs/app_stats_tab.html +2 -2
- django_cfg/templates/admin/snippets/tabs/commands_tab.html +48 -0
- django_cfg/templates/admin/snippets/tabs/documentation_tab.html +1 -190
- django_cfg/templates/admin/snippets/tabs/overview_tab.html +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/METADATA +1 -1
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/RECORD +47 -39
- django_cfg/static/admin/js/commands.js +0 -171
- django_cfg/static/admin/js/dashboard.js +0 -126
- django_cfg/templates/admin/components/management_commands.js +0 -375
- django_cfg/templates/admin/snippets/components/CHARTS_GUIDE.md +0 -322
- django_cfg/templates/admin/snippets/components/recent_activity.html +0 -35
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.75.dist-info → django_cfg-1.4.76.dist-info}/licenses/LICENSE +0 -0
django_cfg/__init__.py
CHANGED
|
@@ -32,7 +32,7 @@ __all__ = [
|
|
|
32
32
|
def __getattr__(name):
|
|
33
33
|
"""Lazy import for agents components."""
|
|
34
34
|
if name == "DjangoAgent":
|
|
35
|
-
from .core.
|
|
35
|
+
from .core.django_agent import DjangoAgent
|
|
36
36
|
return DjangoAgent
|
|
37
37
|
elif name == "SimpleOrchestrator":
|
|
38
38
|
from .core.orchestrator import SimpleOrchestrator
|
|
@@ -7,7 +7,7 @@ from typing import Any, Dict, List, Optional, Type
|
|
|
7
7
|
|
|
8
8
|
from django.contrib.auth.models import User
|
|
9
9
|
|
|
10
|
-
from ..core.
|
|
10
|
+
from ..core.django_agent import DjangoAgent
|
|
11
11
|
from ..core.dependencies import DjangoDeps
|
|
12
12
|
from ..core.orchestrator import SimpleOrchestrator
|
|
13
13
|
from ..models.registry import AgentDefinition
|
|
@@ -6,7 +6,7 @@ from typing import Any, Dict, List
|
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field
|
|
8
8
|
|
|
9
|
-
from ..core.
|
|
9
|
+
from ..core.django_agent import DjangoAgent
|
|
10
10
|
from ..core.dependencies import ContentDeps, RunContext
|
|
11
11
|
from ..core.models import ValidationResult
|
|
12
12
|
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
parseInt(document.getElementById('pub-ack-timeout').value)
|
|
290
290
|
).then(result => {
|
|
291
291
|
if (result.success) {
|
|
292
|
-
alert('Message published successfully
|
|
292
|
+
alert('Message published successfully!\nMessage ID: ' + result.message_id + '\nDelivered: ' + result.delivered + '\nACKs: ' + result.acks_received);
|
|
293
293
|
} else {
|
|
294
294
|
alert('Failed to publish message: ' + result.error);
|
|
295
295
|
}
|
|
@@ -4,12 +4,25 @@ Dashboard view for Centrifugo monitoring.
|
|
|
4
4
|
|
|
5
5
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
6
6
|
from django.shortcuts import render
|
|
7
|
+
from django.urls import reverse
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
@staff_member_required
|
|
10
11
|
def dashboard_view(request):
|
|
11
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
|
+
|
|
12
24
|
context = {
|
|
13
25
|
'page_title': 'Centrifugo Monitor Dashboard',
|
|
26
|
+
'centrifugo_nav_items': nav_items,
|
|
14
27
|
}
|
|
15
28
|
return render(request, 'django_cfg_centrifugo/pages/dashboard.html', context)
|
|
@@ -119,10 +119,11 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
119
119
|
raise ValueError("Centrifugo not configured")
|
|
120
120
|
|
|
121
121
|
headers = {"Content-Type": "application/json"}
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
# Use centrifugo_api_key for direct Centrifugo API calls
|
|
123
|
+
if config.centrifugo_api_key:
|
|
124
|
+
headers["X-API-Key"] = config.centrifugo_api_key
|
|
124
125
|
|
|
125
|
-
# Use wrapper URL as base
|
|
126
|
+
# Use wrapper URL as base (which points to Centrifugo in local setup)
|
|
126
127
|
base_url = config.wrapper_url.rstrip("/")
|
|
127
128
|
|
|
128
129
|
self._http_client = httpx.AsyncClient(
|
|
@@ -300,7 +301,7 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
300
301
|
self, channel: str, data: Dict[str, Any], wait_for_ack: bool, ack_timeout: int
|
|
301
302
|
) -> Dict[str, Any]:
|
|
302
303
|
"""
|
|
303
|
-
Publish message to
|
|
304
|
+
Publish message to Centrifugo API.
|
|
304
305
|
|
|
305
306
|
Args:
|
|
306
307
|
channel: Target channel
|
|
@@ -309,20 +310,78 @@ class CentrifugoTestingAPIViewSet(viewsets.ViewSet):
|
|
|
309
310
|
ack_timeout: ACK timeout in seconds
|
|
310
311
|
|
|
311
312
|
Returns:
|
|
312
|
-
|
|
313
|
+
Centrifugo API response formatted for wrapper compatibility
|
|
313
314
|
"""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
315
|
+
import uuid
|
|
316
|
+
import time
|
|
317
|
+
from ..services.logging import CentrifugoLogger
|
|
318
|
+
|
|
319
|
+
# Generate unique message ID
|
|
320
|
+
message_id = str(uuid.uuid4())
|
|
321
|
+
start_time = time.time()
|
|
322
|
+
|
|
323
|
+
# Create log entry
|
|
324
|
+
log_entry = await CentrifugoLogger.create_log_async(
|
|
325
|
+
message_id=message_id,
|
|
326
|
+
channel=channel,
|
|
327
|
+
data=data,
|
|
328
|
+
wait_for_ack=wait_for_ack,
|
|
329
|
+
ack_timeout=ack_timeout if wait_for_ack else None,
|
|
330
|
+
is_notification=True,
|
|
331
|
+
user=self.request.user if self.request and self.request.user.is_authenticated else None,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
# Centrifugo API format: POST /api with method in body
|
|
336
|
+
payload = {
|
|
337
|
+
"method": "publish",
|
|
338
|
+
"params": {
|
|
339
|
+
"channel": channel,
|
|
340
|
+
"data": data,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
319
343
|
|
|
320
|
-
|
|
321
|
-
|
|
344
|
+
response = await self.http_client.post("/api", json=payload)
|
|
345
|
+
response.raise_for_status()
|
|
346
|
+
result = response.json()
|
|
322
347
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
# Calculate duration
|
|
349
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
350
|
+
|
|
351
|
+
# Mark as success
|
|
352
|
+
if log_entry:
|
|
353
|
+
await CentrifugoLogger.mark_success_async(
|
|
354
|
+
log_entry,
|
|
355
|
+
acks_received=0,
|
|
356
|
+
duration_ms=duration_ms,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# Transform Centrifugo response to match wrapper API format
|
|
360
|
+
return {
|
|
361
|
+
"published": True,
|
|
362
|
+
"message_id": message_id,
|
|
363
|
+
"channel": channel,
|
|
364
|
+
"acks_received": 0,
|
|
365
|
+
"delivered": True,
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
# Calculate duration
|
|
370
|
+
duration_ms = int((time.time() - start_time) * 1000)
|
|
371
|
+
|
|
372
|
+
# Mark as failed
|
|
373
|
+
if log_entry:
|
|
374
|
+
from asgiref.sync import sync_to_async
|
|
375
|
+
from ..models import CentrifugoLog
|
|
376
|
+
|
|
377
|
+
await sync_to_async(CentrifugoLog.objects.mark_failed)(
|
|
378
|
+
log_instance=log_entry,
|
|
379
|
+
error_code=type(e).__name__,
|
|
380
|
+
error_message=str(e),
|
|
381
|
+
duration_ms=duration_ms,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
raise
|
|
326
385
|
|
|
327
386
|
async def _send_ack_to_wrapper(
|
|
328
387
|
self, message_id: str, client_id: str
|
|
@@ -33,12 +33,12 @@ def dashboard_view(request):
|
|
|
33
33
|
{
|
|
34
34
|
'label': 'Task History',
|
|
35
35
|
'url': '/admin/django_dramatiq/task/',
|
|
36
|
-
'icon': '
|
|
36
|
+
'icon': 'history',
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
'label': 'Settings',
|
|
40
40
|
'url': '/admin/constance/config/',
|
|
41
|
-
'icon': '
|
|
41
|
+
'icon': 'settings',
|
|
42
42
|
},
|
|
43
43
|
]
|
|
44
44
|
|
|
@@ -59,12 +59,12 @@ def dashboard_view(request):
|
|
|
59
59
|
{
|
|
60
60
|
'label': 'Task History',
|
|
61
61
|
'url': '/admin/django_dramatiq/task/',
|
|
62
|
-
'icon': '
|
|
62
|
+
'icon': 'history',
|
|
63
63
|
},
|
|
64
64
|
{
|
|
65
65
|
'label': 'Settings',
|
|
66
66
|
'url': '/admin/constance/config/',
|
|
67
|
-
'icon': '
|
|
67
|
+
'icon': 'settings',
|
|
68
68
|
},
|
|
69
69
|
]
|
|
70
70
|
|
|
@@ -220,9 +220,12 @@ class APIFrameworksGenerator:
|
|
|
220
220
|
Dictionary with Spectacular settings
|
|
221
221
|
"""
|
|
222
222
|
# Import authentication extension to register it with drf-spectacular
|
|
223
|
+
# Only import if apps are ready to avoid warnings
|
|
223
224
|
try:
|
|
224
|
-
from
|
|
225
|
-
|
|
225
|
+
from django.apps import apps
|
|
226
|
+
if apps.ready:
|
|
227
|
+
from django_cfg.middleware import authentication # noqa: F401
|
|
228
|
+
except (ImportError, RuntimeError):
|
|
226
229
|
pass
|
|
227
230
|
|
|
228
231
|
# Check if Spectacular settings exist (from OpenAPI Client or elsewhere)
|
|
@@ -13,7 +13,7 @@ import json
|
|
|
13
13
|
from django.core.management.base import BaseCommand
|
|
14
14
|
from django.urls import reverse
|
|
15
15
|
|
|
16
|
-
from django_cfg.apps.api.endpoints.checker import check_all_endpoints
|
|
16
|
+
from django_cfg.apps.api.endpoints.endpoints_status.checker import check_all_endpoints
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class Command(BaseCommand):
|
|
@@ -67,13 +67,13 @@ Custom navbar (extending):
|
|
|
67
67
|
<nav class="hidden md:flex items-center space-x-1">
|
|
68
68
|
{% for item in nav_items %}
|
|
69
69
|
<a href="{{ item.url }}"
|
|
70
|
-
class="px-3 py-2 rounded-md text-sm font-medium transition-colors
|
|
70
|
+
class="px-3 py-2 rounded-md text-sm font-medium transition-colors flex items-center gap-1.5
|
|
71
71
|
{% if item.active %}
|
|
72
72
|
bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-white
|
|
73
73
|
{% else %}
|
|
74
74
|
text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-white
|
|
75
75
|
{% endif %}">
|
|
76
|
-
{% if item.icon %}<span class="
|
|
76
|
+
{% if item.icon %}<span class="material-icons text-sm">{{ item.icon }}</span>{% endif %}
|
|
77
77
|
{{ item.label }}
|
|
78
78
|
</a>
|
|
79
79
|
{% endfor %}
|
|
@@ -8,10 +8,11 @@ import json
|
|
|
8
8
|
import logging
|
|
9
9
|
from typing import Any, Dict
|
|
10
10
|
|
|
11
|
+
from django.template.loader import render_to_string
|
|
11
12
|
from django.utils import timezone
|
|
12
13
|
|
|
13
14
|
from django_cfg.core.state import get_current_config
|
|
14
|
-
from django_cfg.modules.django_dashboard.debug import
|
|
15
|
+
from django_cfg.modules.django_dashboard.debug import save_dashboard_render
|
|
15
16
|
from django_cfg.modules.django_dashboard.sections.commands import CommandsSection
|
|
16
17
|
from django_cfg.modules.django_dashboard.sections.documentation import DocumentationSection
|
|
17
18
|
|
|
@@ -113,48 +114,41 @@ class UnfoldCallbacks(
|
|
|
113
114
|
time_range=time_range,
|
|
114
115
|
navigation=navigation
|
|
115
116
|
)
|
|
116
|
-
# Debug: save render (only in debug mode)
|
|
117
|
-
if config and config.debug:
|
|
118
|
-
save_section_render('overview', overview_section)
|
|
119
117
|
except Exception as e:
|
|
120
118
|
logger.error(f"Failed to render overview section: {e}", exc_info=True)
|
|
121
119
|
overview_section = None
|
|
122
120
|
|
|
123
121
|
try:
|
|
124
122
|
stats_section = StatsSection(request).render()
|
|
125
|
-
# Debug: save render (only in debug mode)
|
|
126
|
-
if config and config.debug:
|
|
127
|
-
save_section_render('stats', stats_section)
|
|
128
123
|
except Exception as e:
|
|
129
124
|
logger.error(f"Failed to render stats section: {e}", exc_info=True)
|
|
130
125
|
stats_section = None
|
|
131
126
|
|
|
132
127
|
try:
|
|
133
128
|
system_section = SystemSection(request).render()
|
|
134
|
-
# Debug: save render (only in debug mode)
|
|
135
|
-
if config and config.debug:
|
|
136
|
-
save_section_render('system', system_section)
|
|
137
129
|
except Exception as e:
|
|
138
130
|
logger.error(f"Failed to render system section: {e}", exc_info=True)
|
|
139
131
|
system_section = None
|
|
140
132
|
|
|
141
133
|
try:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
134
|
+
# Generate documentation content first
|
|
135
|
+
doc_section = DocumentationSection(request)
|
|
136
|
+
doc_data = doc_section.get_data_old() # Get markdown/HTML content
|
|
137
|
+
documentation_content = doc_data.get('documentation_content') or doc_data.get('readme_content', '')
|
|
138
|
+
documentation_section = doc_section.render()
|
|
146
139
|
except Exception as e:
|
|
147
|
-
logger.error(f"Failed to render
|
|
148
|
-
|
|
140
|
+
logger.error(f"Failed to render documentation section: {e}", exc_info=True)
|
|
141
|
+
documentation_section = None
|
|
142
|
+
documentation_content = None
|
|
149
143
|
|
|
150
144
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
# Render commands section with documentation content
|
|
146
|
+
commands_section = CommandsSection(request).render(
|
|
147
|
+
documentation_content=documentation_content
|
|
148
|
+
)
|
|
155
149
|
except Exception as e:
|
|
156
|
-
logger.error(f"Failed to render
|
|
157
|
-
|
|
150
|
+
logger.error(f"Failed to render commands section: {e}", exc_info=True)
|
|
151
|
+
commands_section = None
|
|
158
152
|
|
|
159
153
|
# Extract custom widgets from context if provided by project's dashboard_callback
|
|
160
154
|
custom_widgets = context.get('custom_widgets', [])
|
|
@@ -172,9 +166,6 @@ class UnfoldCallbacks(
|
|
|
172
166
|
custom_widgets=custom_widgets,
|
|
173
167
|
custom_metrics=custom_metrics
|
|
174
168
|
)
|
|
175
|
-
# Debug: save render (only in debug mode)
|
|
176
|
-
if config and config.debug:
|
|
177
|
-
save_section_render('widgets', widgets_section)
|
|
178
169
|
except Exception as e:
|
|
179
170
|
logger.error(f"Failed to render widgets section: {e}", exc_info=True)
|
|
180
171
|
widgets_section = None
|
|
@@ -202,6 +193,9 @@ class UnfoldCallbacks(
|
|
|
202
193
|
"documentation_section": documentation_section,
|
|
203
194
|
"widgets_section": widgets_section,
|
|
204
195
|
|
|
196
|
+
# Documentation content for commands tab
|
|
197
|
+
"documentation_content": documentation_content,
|
|
198
|
+
|
|
205
199
|
# Statistics cards
|
|
206
200
|
"cards": cards_data,
|
|
207
201
|
"user_stats": [card.to_dict() for card in user_stats],
|
|
@@ -296,6 +290,14 @@ class UnfoldCallbacks(
|
|
|
296
290
|
# activity_tracker_data = context.get('activity_tracker', [])
|
|
297
291
|
# logger.info(f"Activity tracker data count: {len(activity_tracker_data)}")
|
|
298
292
|
|
|
293
|
+
# Debug: save full rendered page (only in debug mode)
|
|
294
|
+
if config and config.debug:
|
|
295
|
+
try:
|
|
296
|
+
full_html = render_to_string('admin/index.html', context, request)
|
|
297
|
+
save_dashboard_render(full_html, name='dashboard_full_page', context=context)
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Failed to save full dashboard render: {e}", exc_info=True)
|
|
300
|
+
|
|
299
301
|
return context
|
|
300
302
|
|
|
301
303
|
except Exception as e:
|
django_cfg/pyproject.toml
CHANGED
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "django-cfg"
|
|
7
|
-
version = "1.4.
|
|
7
|
+
version = "1.4.76"
|
|
8
8
|
description = "Django AI framework with built-in agents, type-safe Pydantic v2 configuration, and 8 enterprise apps. Replace settings.py, validate at startup, 90% less code. Production-ready AI workflows for Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
keywords = [ "django", "configuration", "pydantic", "settings", "type-safety", "pydantic-settings", "django-environ", "startup-validation", "ide-autocomplete", "ai-agents", "enterprise-django", "django-settings", "type-safe-config",]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constance Settings UI Styles
|
|
3
|
+
*
|
|
4
|
+
* Enhanced UI for Django Constance dynamic settings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Smooth transitions for all interactive elements */
|
|
8
|
+
.constance-expandable {
|
|
9
|
+
transition: all 0.2s ease-in-out;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Better hover states */
|
|
13
|
+
.constance tr:hover {
|
|
14
|
+
background-color: rgba(0, 0, 0, 0.02);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.dark .constance tr:hover {
|
|
18
|
+
background-color: rgba(255, 255, 255, 0.02);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/* Improved focus states for accessibility */
|
|
22
|
+
.constance a:focus,
|
|
23
|
+
.constance button:focus {
|
|
24
|
+
outline: 2px solid rgb(59, 130, 246);
|
|
25
|
+
outline-offset: 2px;
|
|
26
|
+
border-radius: 4px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Better spacing for long text */
|
|
30
|
+
.constance .break-words {
|
|
31
|
+
word-break: break-word;
|
|
32
|
+
overflow-wrap: break-word;
|
|
33
|
+
hyphens: auto;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Smooth animations for expand/collapse */
|
|
37
|
+
.constance [x-show] {
|
|
38
|
+
transition: opacity 0.2s ease-in-out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Icon rotation animation */
|
|
42
|
+
.constance .material-symbols-outlined {
|
|
43
|
+
transition: transform 0.3s ease-in-out;
|
|
44
|
+
}
|
|
@@ -1,178 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Django CFG Dashboard Styles
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Dashboard-specific styling
|
|
5
|
+
*
|
|
6
|
+
* NOTE: Common styles moved to separate files:
|
|
7
|
+
* - Tab styles: /static/admin/css/tabs.css
|
|
8
|
+
* - Theme styles: /static/admin/css/theme.css
|
|
9
|
+
* - Layout (including .overview-grid): /static/admin/css/layout.css
|
|
5
10
|
*/
|
|
6
11
|
|
|
7
|
-
/* =============================================================================
|
|
8
|
-
Dashboard Tab Styles
|
|
9
|
-
============================================================================= */
|
|
10
|
-
|
|
11
|
-
.tab-content {
|
|
12
|
-
display: none;
|
|
13
|
-
animation: fadeIn 0.3s ease-in-out;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.tab-content.active {
|
|
17
|
-
display: block;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
#dashboard-tabs button {
|
|
21
|
-
cursor: pointer;
|
|
22
|
-
transition: all 0.2s ease-in-out;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
@keyframes fadeIn {
|
|
26
|
-
from {
|
|
27
|
-
opacity: 0;
|
|
28
|
-
transform: translateY(10px);
|
|
29
|
-
}
|
|
30
|
-
to {
|
|
31
|
-
opacity: 1;
|
|
32
|
-
transform: translateY(0);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/* =============================================================================
|
|
37
|
-
Theme Cards - Cross-theme Compatible
|
|
38
|
-
============================================================================= */
|
|
39
|
-
|
|
40
|
-
.theme-card {
|
|
41
|
-
background-color: rgba(255, 255, 255, 0.2) !important;
|
|
42
|
-
backdrop-filter: blur(10px) !important;
|
|
43
|
-
color: #111827 !important;
|
|
44
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
45
|
-
border-radius: 10px !important;
|
|
46
|
-
transition: all 0.2s ease;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
html.dark .theme-card {
|
|
50
|
-
background-color: rgba(31, 41, 55, 0.2) !important;
|
|
51
|
-
backdrop-filter: blur(10px) !important;
|
|
52
|
-
color: white !important;
|
|
53
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
54
|
-
border-radius: 10px !important;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
.theme-text {
|
|
58
|
-
color: #111827 !important;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
html.dark .theme-text {
|
|
62
|
-
color: white !important;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.theme-border {
|
|
66
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
html.dark .theme-border {
|
|
70
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/* =============================================================================
|
|
74
|
-
Grid Layout
|
|
75
|
-
============================================================================= */
|
|
76
|
-
|
|
77
|
-
.overview-grid {
|
|
78
|
-
display: grid !important;
|
|
79
|
-
grid-template-columns: 1fr !important;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
@media (min-width: 1280px) {
|
|
83
|
-
.overview-grid {
|
|
84
|
-
grid-template-columns: 2fr 1fr !important;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/* =============================================================================
|
|
89
|
-
Tab Navigation Styling
|
|
90
|
-
============================================================================= */
|
|
91
|
-
|
|
92
|
-
#dashboard-tabs {
|
|
93
|
-
border-bottom: 2px solid #e5e7eb !important;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
html.dark #dashboard-tabs {
|
|
97
|
-
border-bottom-color: #374151 !important;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#dashboard-tabs button {
|
|
101
|
-
background-color: #f3f4f6 !important;
|
|
102
|
-
color: #6b7280 !important;
|
|
103
|
-
border-bottom: 2px solid transparent !important;
|
|
104
|
-
transition: all 0.2s ease !important;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
html.dark #dashboard-tabs button {
|
|
108
|
-
background-color: #374151 !important;
|
|
109
|
-
color: #9ca3af !important;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#dashboard-tabs button:hover {
|
|
113
|
-
background-color: #e5e7eb !important;
|
|
114
|
-
color: #374151 !important;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
html.dark #dashboard-tabs button:hover {
|
|
118
|
-
background-color: #4b5563 !important;
|
|
119
|
-
color: #d1d5db !important;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/* Active tab */
|
|
123
|
-
#dashboard-tabs button.active,
|
|
124
|
-
#dashboard-tabs button[class*="bg-blue"] {
|
|
125
|
-
background-color: #2563eb !important;
|
|
126
|
-
color: white !important;
|
|
127
|
-
border-bottom-color: #2563eb !important;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
html.dark #dashboard-tabs button.active,
|
|
131
|
-
html.dark #dashboard-tabs button[class*="bg-blue"] {
|
|
132
|
-
background-color: #3b82f6 !important;
|
|
133
|
-
color: white !important;
|
|
134
|
-
border-bottom-color: #3b82f6 !important;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* =============================================================================
|
|
138
|
-
Universal Card Borders - Transparent Cross-theme
|
|
139
|
-
============================================================================= */
|
|
140
|
-
|
|
141
|
-
.card-border,
|
|
142
|
-
.border-base-200,
|
|
143
|
-
[class*="border-base-200"],
|
|
144
|
-
[class*="dark:border-base-700"] {
|
|
145
|
-
border-color: rgba(229, 231, 235, 0.6) !important;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
html.dark .card-border,
|
|
149
|
-
html.dark .border-base-200,
|
|
150
|
-
html.dark [class*="border-base-200"],
|
|
151
|
-
html.dark [class*="dark:border-base-700"] {
|
|
152
|
-
border-color: rgba(55, 65, 81, 0.6) !important;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/* =============================================================================
|
|
156
|
-
Icon Spacing Defaults
|
|
157
|
-
============================================================================= */
|
|
158
|
-
|
|
159
|
-
.theme-card .material-icons:not(.no-margin) {
|
|
160
|
-
margin-right: 0.75rem !important; /* 12px default spacing */
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
.theme-card .flex.items-center .material-icons {
|
|
164
|
-
margin-right: 0.75rem !important;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
.theme-card .status-badge .material-icons {
|
|
168
|
-
margin-right: 0.25rem !important; /* 4px for badges */
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
.theme-card button .material-icons,
|
|
172
|
-
.theme-card a .material-icons {
|
|
173
|
-
margin-right: 0.5rem !important;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
12
|
/* =============================================================================
|
|
177
13
|
Section Specific Styles
|
|
178
14
|
============================================================================= */
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Styles
|
|
3
|
+
* Grid layouts, containers, and responsive design
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* Overview Grid Layout */
|
|
7
|
+
.overview-grid {
|
|
8
|
+
display: grid !important;
|
|
9
|
+
grid-template-columns: 1fr !important;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@media (min-width: 1280px) {
|
|
13
|
+
.overview-grid {
|
|
14
|
+
grid-template-columns: 2fr 1fr !important;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Alpine.js Cloak - Hide elements until Alpine initializes */
|
|
19
|
+
[x-cloak] {
|
|
20
|
+
display: none !important;
|
|
21
|
+
}
|