django-cfg 1.4.111__py3-none-any.whl → 1.4.114__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/dashboard/serializers/__init__.py +12 -0
- django_cfg/apps/dashboard/serializers/django_q2.py +50 -0
- django_cfg/apps/dashboard/serializers/overview.py +22 -11
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/apps/dashboard/services/django_q2_service.py +159 -0
- django_cfg/apps/dashboard/services/system_health_service.py +85 -0
- django_cfg/apps/dashboard/urls.py +2 -0
- django_cfg/apps/dashboard/views/__init__.py +2 -0
- django_cfg/apps/dashboard/views/django_q2_views.py +79 -0
- django_cfg/apps/dashboard/views/overview_views.py +16 -2
- django_cfg/apps/tasks/migrations/0002_delete_tasklog.py +16 -0
- django_cfg/config.py +3 -4
- django_cfg/core/base/config_model.py +18 -1
- django_cfg/core/builders/apps_builder.py +4 -0
- django_cfg/core/generation/data_generators/cache.py +28 -2
- django_cfg/core/generation/integration_generators/__init__.py +4 -0
- django_cfg/core/generation/integration_generators/django_q2.py +133 -0
- django_cfg/core/generation/orchestrator.py +13 -0
- django_cfg/core/integration/display/startup.py +2 -2
- django_cfg/core/integration/url_integration.py +2 -2
- django_cfg/models/__init__.py +3 -0
- django_cfg/models/django/__init__.py +3 -0
- django_cfg/models/django/django_q2.py +491 -0
- django_cfg/modules/django_admin/utils/html_builder.py +50 -2
- django_cfg/pyproject.toml +2 -2
- django_cfg/registry/core.py +4 -0
- django_cfg/static/frontend/admin.zip +0 -0
- django_cfg/templates/admin/index.html +389 -166
- django_cfg/templatetags/django_cfg.py +8 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/METADATA +3 -1
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/RECORD +35 -29
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.111.dist-info → django_cfg-1.4.114.dist-info}/licenses/LICENSE +0 -0
|
@@ -79,6 +79,14 @@
|
|
|
79
79
|
border: 1px solid rgba(209, 213, 219, 0.2);
|
|
80
80
|
border-radius: 0.375rem; /* rounded-md */
|
|
81
81
|
overflow: hidden;
|
|
82
|
+
min-height: 400px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* On mobile, use more screen space */
|
|
86
|
+
@media (max-width: 768px) {
|
|
87
|
+
.iframe-container {
|
|
88
|
+
min-height: calc(100vh - 200px);
|
|
89
|
+
}
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
.dark .iframe-container {
|
|
@@ -147,7 +155,15 @@
|
|
|
147
155
|
},
|
|
148
156
|
resetIframe(tab) {
|
|
149
157
|
// Reset the iframe that was just hidden
|
|
150
|
-
|
|
158
|
+
let iframeId;
|
|
159
|
+
if (tab === 'builtin') {
|
|
160
|
+
iframeId = 'nextjs-dashboard-iframe-builtin';
|
|
161
|
+
} else if (tab === 'nextjs') {
|
|
162
|
+
iframeId = 'nextjs-dashboard-iframe-nextjs';
|
|
163
|
+
} else if (tab === 'docs') {
|
|
164
|
+
iframeId = 'nextjs-dashboard-iframe-docs';
|
|
165
|
+
}
|
|
166
|
+
|
|
151
167
|
const iframe = document.getElementById(iframeId);
|
|
152
168
|
if (iframe) {
|
|
153
169
|
const originalSrc = iframe.getAttribute('data-original-src') || iframe.src;
|
|
@@ -159,15 +175,24 @@
|
|
|
159
175
|
}
|
|
160
176
|
},
|
|
161
177
|
openInNewWindow() {
|
|
162
|
-
// Get
|
|
163
|
-
|
|
178
|
+
// Get iframe ID based on active tab
|
|
179
|
+
let iframeId;
|
|
180
|
+
if (this.activeTab === 'builtin') {
|
|
181
|
+
iframeId = 'nextjs-dashboard-iframe-builtin';
|
|
182
|
+
} else if (this.activeTab === 'nextjs') {
|
|
183
|
+
iframeId = 'nextjs-dashboard-iframe-nextjs';
|
|
184
|
+
} else if (this.activeTab === 'docs') {
|
|
185
|
+
iframeId = 'nextjs-dashboard-iframe-docs';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const iframe = document.getElementById(iframeId);
|
|
164
189
|
if (!iframe) return;
|
|
165
190
|
|
|
166
191
|
// Get base URL from iframe src or data-original-src
|
|
167
192
|
let baseUrl = iframe.src || iframe.getAttribute('data-original-src');
|
|
168
193
|
|
|
169
|
-
// If we have a tracked path from postMessage, use it
|
|
170
|
-
if (this.currentNextjsPath) {
|
|
194
|
+
// If we have a tracked path from postMessage (only for nextjs tab), use it
|
|
195
|
+
if (this.activeTab === 'nextjs' && this.currentNextjsPath) {
|
|
171
196
|
try {
|
|
172
197
|
const url = new URL(baseUrl);
|
|
173
198
|
// Replace pathname with tracked path
|
|
@@ -207,16 +232,43 @@
|
|
|
207
232
|
.dark [style*="border-bottom-color"] {
|
|
208
233
|
border-bottom-color: rgba(75, 85, 99, 0.2) !important;
|
|
209
234
|
}
|
|
235
|
+
|
|
236
|
+
/* Mobile: Scrollable tabs */
|
|
237
|
+
.tabs-container {
|
|
238
|
+
overflow-x: auto;
|
|
239
|
+
-webkit-overflow-scrolling: touch;
|
|
240
|
+
scrollbar-width: thin;
|
|
241
|
+
scrollbar-color: rgba(156, 163, 175, 0.3) transparent;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.tabs-container::-webkit-scrollbar {
|
|
245
|
+
height: 4px;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.tabs-container::-webkit-scrollbar-track {
|
|
249
|
+
background: transparent;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.tabs-container::-webkit-scrollbar-thumb {
|
|
253
|
+
background-color: rgba(156, 163, 175, 0.3);
|
|
254
|
+
border-radius: 2px;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.tabs-container::-webkit-scrollbar-thumb:hover {
|
|
258
|
+
background-color: rgba(156, 163, 175, 0.5);
|
|
259
|
+
}
|
|
210
260
|
</style>
|
|
211
|
-
<div class="
|
|
212
|
-
<
|
|
261
|
+
<div class="tabs-container">
|
|
262
|
+
<div class="flex items-center justify-between px-4 min-w-max">
|
|
263
|
+
<nav class="-mb-px flex space-x-2 sm:space-x-4 md:space-x-8" aria-label="Dashboard Tabs">
|
|
213
264
|
<button @click="switchTab('builtin')"
|
|
214
265
|
class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
|
|
215
266
|
:class="activeTab === 'builtin'
|
|
216
267
|
? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
|
|
217
268
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:border-gray-700'">
|
|
218
269
|
<span class="material-icons text-base">dashboard</span>
|
|
219
|
-
<span>Built-in Dashboard</span>
|
|
270
|
+
<span class="hidden sm:inline">Built-in Dashboard</span>
|
|
271
|
+
<span class="sm:hidden">Built-in</span>
|
|
220
272
|
</button>
|
|
221
273
|
|
|
222
274
|
<button @click="switchTab('nextjs')"
|
|
@@ -225,32 +277,41 @@
|
|
|
225
277
|
? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
|
|
226
278
|
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:border-gray-700'">
|
|
227
279
|
<span class="material-icons text-base">web</span>
|
|
228
|
-
<span>{% nextjs_external_admin_title %}</span>
|
|
229
|
-
|
|
230
|
-
</nav>
|
|
231
|
-
|
|
232
|
-
<!-- Actions & Version info -->
|
|
233
|
-
<div class="flex items-center gap-4 py-4">
|
|
234
|
-
<!-- Open in new window button (only for External Admin tab) -->
|
|
235
|
-
<button @click="openInNewWindow()"
|
|
236
|
-
x-show="activeTab === 'nextjs'"
|
|
237
|
-
title="Open in new window"
|
|
238
|
-
class="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-700 transition-all duration-150"
|
|
239
|
-
style="display: none;">
|
|
240
|
-
<span class="material-icons" style="font-size: 16px;">open_in_new</span>
|
|
241
|
-
<span>Open in New Window</span>
|
|
280
|
+
<span class="hidden sm:inline">{% nextjs_external_admin_title %}</span>
|
|
281
|
+
<span class="sm:hidden">Admin</span>
|
|
242
282
|
</button>
|
|
243
283
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
</
|
|
284
|
+
<button @click="switchTab('docs')"
|
|
285
|
+
class="whitespace-nowrap py-4 px-2 border-b-2 font-medium text-sm flex items-center gap-2 transition-all duration-200"
|
|
286
|
+
:class="activeTab === 'docs'
|
|
287
|
+
? 'border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500'
|
|
288
|
+
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 dark:text-gray-400 dark:hover:text-gray-300 dark:hover:border-gray-700'">
|
|
289
|
+
<span class="material-icons text-base">description</span>
|
|
290
|
+
<span>Docs</span>
|
|
291
|
+
</button>
|
|
292
|
+
</nav>
|
|
293
|
+
|
|
294
|
+
<!-- Actions & Version info -->
|
|
295
|
+
<div class="flex items-center gap-2 md:gap-4 py-4">
|
|
296
|
+
<!-- Open in new window button (available for all tabs) -->
|
|
297
|
+
<button @click="openInNewWindow()"
|
|
298
|
+
title="Open in new window"
|
|
299
|
+
class="flex items-center gap-1.5 px-2 md:px-3 py-1.5 text-xs font-medium rounded-md text-gray-600 hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-100 dark:hover:bg-gray-700 transition-all duration-150 whitespace-nowrap">
|
|
300
|
+
<span class="material-icons" style="font-size: 16px;">open_in_new</span>
|
|
301
|
+
<span class="hidden sm:inline">Open in New Window</span>
|
|
302
|
+
</button>
|
|
303
|
+
|
|
304
|
+
<!-- Version info -->
|
|
305
|
+
<div class="text-xs text-gray-400 dark:text-gray-500 hidden md:block">
|
|
306
|
+
{% load django_cfg %}
|
|
307
|
+
<a href="{% lib_site_url %}" class="text-blue-600 hover:text-blue-700">
|
|
308
|
+
{% lib_name %}
|
|
309
|
+
</a>
|
|
310
|
+
</div>
|
|
311
|
+
</div>
|
|
250
312
|
</div>
|
|
251
313
|
</div>
|
|
252
314
|
</div>
|
|
253
|
-
</div>
|
|
254
315
|
</div>
|
|
255
316
|
{% endif %}
|
|
256
317
|
|
|
@@ -293,6 +354,25 @@
|
|
|
293
354
|
</div>
|
|
294
355
|
</div>
|
|
295
356
|
{% endif %}
|
|
357
|
+
|
|
358
|
+
<!-- Docs Tab Content -->
|
|
359
|
+
<div x-show="activeTab === 'docs'" style="display: none;">
|
|
360
|
+
<div class="iframe-container">
|
|
361
|
+
<div class="iframe-loading" id="iframe-loading-docs">
|
|
362
|
+
<div class="spinner"></div>
|
|
363
|
+
<p id="loading-text-docs">Loading documentation...</p>
|
|
364
|
+
</div>
|
|
365
|
+
|
|
366
|
+
<iframe
|
|
367
|
+
id="nextjs-dashboard-iframe-docs"
|
|
368
|
+
class="nextjs-dashboard-iframe"
|
|
369
|
+
src="{% lib_docs_url %}"
|
|
370
|
+
data-original-src="{% lib_docs_url %}"
|
|
371
|
+
title="Documentation"
|
|
372
|
+
sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-storage-access-by-user-activation"
|
|
373
|
+
></iframe>
|
|
374
|
+
</div>
|
|
375
|
+
</div>
|
|
296
376
|
</div>
|
|
297
377
|
{% endcomponent %}
|
|
298
378
|
{% endblock %}
|
|
@@ -301,185 +381,328 @@
|
|
|
301
381
|
{{ block.super }}
|
|
302
382
|
<script>
|
|
303
383
|
(function() {
|
|
304
|
-
|
|
305
|
-
const iframeBuiltin = document.getElementById('nextjs-dashboard-iframe-builtin');
|
|
306
|
-
const loadingBuiltin = document.getElementById('iframe-loading-builtin');
|
|
307
|
-
|
|
308
|
-
// External Next.js admin iframe (if exists)
|
|
309
|
-
const iframeNextjs = document.getElementById('nextjs-dashboard-iframe-nextjs');
|
|
310
|
-
const loadingNextjs = document.getElementById('iframe-loading-nextjs');
|
|
311
|
-
|
|
312
|
-
// Setup both iframes immediately (always loaded, not lazy-loaded)
|
|
313
|
-
if (iframeBuiltin) {
|
|
314
|
-
setupIframe(iframeBuiltin, loadingBuiltin);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (iframeNextjs) {
|
|
318
|
-
setupIframe(iframeNextjs, loadingNextjs);
|
|
319
|
-
}
|
|
384
|
+
'use strict';
|
|
320
385
|
|
|
321
386
|
/**
|
|
322
|
-
*
|
|
387
|
+
* MessageBridge - Handles postMessage communication with internal Next.js iframes
|
|
323
388
|
*/
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!iframeSrc) {
|
|
330
|
-
console.warn('[Django-CFG] iframe has no src attribute');
|
|
331
|
-
return;
|
|
389
|
+
class MessageBridge {
|
|
390
|
+
constructor(iframe, origin) {
|
|
391
|
+
this.iframe = iframe;
|
|
392
|
+
this.origin = origin;
|
|
393
|
+
this.themeObserver = null;
|
|
332
394
|
}
|
|
333
395
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
// iframe load event
|
|
341
|
-
iframe.addEventListener('load', function() {
|
|
342
|
-
setTimeout(() => {
|
|
343
|
-
sendDataToIframe();
|
|
344
|
-
}, 100);
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// Send theme and auth data to iframe
|
|
348
|
-
function sendDataToIframe() {
|
|
349
|
-
if (!iframe || !iframe.contentWindow) {
|
|
350
|
-
console.error('[Django-CFG] iframe or contentWindow not available');
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
396
|
+
/**
|
|
397
|
+
* Send theme data to iframe
|
|
398
|
+
*/
|
|
399
|
+
sendTheme() {
|
|
400
|
+
if (!this.iframe?.contentWindow) return;
|
|
353
401
|
|
|
354
402
|
const htmlElement = document.documentElement;
|
|
355
403
|
const isDark = htmlElement.classList.contains('dark');
|
|
404
|
+
const themeMode = this._getThemeMode();
|
|
356
405
|
|
|
357
|
-
// Get theme mode from Alpine.js
|
|
358
|
-
let themeMode = 'auto';
|
|
359
|
-
if (window.Alpine && window.Alpine.$data) {
|
|
360
|
-
try {
|
|
361
|
-
const alpineData = window.Alpine.$data(htmlElement);
|
|
362
|
-
if (alpineData && alpineData.adminTheme) {
|
|
363
|
-
themeMode = alpineData.adminTheme;
|
|
364
|
-
}
|
|
365
|
-
} catch (e) {
|
|
366
|
-
console.warn('[Django-CFG] Failed to get Alpine data:', e);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Get JWT tokens from localStorage
|
|
371
|
-
const authToken = localStorage.getItem('auth_token');
|
|
372
|
-
const refreshToken = localStorage.getItem('refresh_token');
|
|
373
|
-
|
|
374
|
-
// Send theme
|
|
375
406
|
try {
|
|
376
|
-
iframe.contentWindow.postMessage({
|
|
407
|
+
this.iframe.contentWindow.postMessage({
|
|
377
408
|
type: 'parent-theme',
|
|
378
409
|
data: {
|
|
379
410
|
theme: isDark ? 'dark' : 'light',
|
|
380
411
|
themeMode: themeMode
|
|
381
412
|
}
|
|
382
|
-
},
|
|
413
|
+
}, this.origin);
|
|
383
414
|
} catch (e) {
|
|
384
|
-
console.error('[Django-CFG] Failed to send theme
|
|
415
|
+
console.error('[Django-CFG] Failed to send theme:', e);
|
|
385
416
|
}
|
|
417
|
+
}
|
|
386
418
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
419
|
+
/**
|
|
420
|
+
* Send auth tokens to iframe
|
|
421
|
+
*/
|
|
422
|
+
sendAuth() {
|
|
423
|
+
if (!this.iframe?.contentWindow) return;
|
|
424
|
+
|
|
425
|
+
const authToken = localStorage.getItem('auth_token');
|
|
426
|
+
const refreshToken = localStorage.getItem('refresh_token');
|
|
427
|
+
|
|
428
|
+
if (!authToken) return;
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
this.iframe.contentWindow.postMessage({
|
|
432
|
+
type: 'parent-auth',
|
|
433
|
+
data: { authToken, refreshToken }
|
|
434
|
+
}, this.origin);
|
|
435
|
+
} catch (e) {
|
|
436
|
+
console.error('[Django-CFG] Failed to send auth:', e);
|
|
400
437
|
}
|
|
401
438
|
}
|
|
402
439
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
440
|
+
/**
|
|
441
|
+
* Send all data (theme + auth)
|
|
442
|
+
*/
|
|
443
|
+
sendAllData() {
|
|
444
|
+
this.sendTheme();
|
|
445
|
+
this.sendAuth();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Start watching for theme changes
|
|
450
|
+
*/
|
|
451
|
+
watchThemeChanges() {
|
|
452
|
+
this.themeObserver = new MutationObserver(() => {
|
|
453
|
+
this.sendTheme();
|
|
409
454
|
});
|
|
410
|
-
});
|
|
411
455
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
456
|
+
this.themeObserver.observe(document.documentElement, {
|
|
457
|
+
attributes: true,
|
|
458
|
+
attributeFilter: ['class']
|
|
459
|
+
});
|
|
460
|
+
}
|
|
416
461
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
462
|
+
/**
|
|
463
|
+
* Stop watching
|
|
464
|
+
*/
|
|
465
|
+
destroy() {
|
|
466
|
+
if (this.themeObserver) {
|
|
467
|
+
this.themeObserver.disconnect();
|
|
468
|
+
this.themeObserver = null;
|
|
421
469
|
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get theme mode from Alpine.js
|
|
474
|
+
*/
|
|
475
|
+
_getThemeMode() {
|
|
476
|
+
if (!window.Alpine?.$$data) return 'auto';
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
const alpineData = window.Alpine.$data(document.documentElement);
|
|
480
|
+
return alpineData?.adminTheme || 'auto';
|
|
481
|
+
} catch (e) {
|
|
482
|
+
return 'auto';
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
422
486
|
|
|
423
|
-
|
|
487
|
+
/**
|
|
488
|
+
* InternalIframeHandler - Handles Next.js internal iframes with postMessage
|
|
489
|
+
*/
|
|
490
|
+
class InternalIframeHandler {
|
|
491
|
+
constructor(iframe, loading, origin) {
|
|
492
|
+
this.iframe = iframe;
|
|
493
|
+
this.loading = loading;
|
|
494
|
+
this.origin = origin;
|
|
495
|
+
this.messageBridge = new MessageBridge(iframe, origin);
|
|
496
|
+
this.messageListener = null;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
init() {
|
|
500
|
+
this._setupLoadHandler();
|
|
501
|
+
this._setupMessageListener();
|
|
502
|
+
this.messageBridge.watchThemeChanges();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
_setupLoadHandler() {
|
|
506
|
+
this.iframe.addEventListener('load', () => {
|
|
507
|
+
setTimeout(() => {
|
|
508
|
+
this.messageBridge.sendAllData();
|
|
509
|
+
}, 100);
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
_setupMessageListener() {
|
|
514
|
+
this.messageListener = (event) => {
|
|
515
|
+
if (event.origin !== this.origin) return;
|
|
516
|
+
|
|
517
|
+
const { type, data } = event.data || {};
|
|
518
|
+
this._handleMessage(type, data);
|
|
519
|
+
};
|
|
424
520
|
|
|
521
|
+
window.addEventListener('message', this.messageListener);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
_handleMessage(type, data) {
|
|
425
525
|
switch (type) {
|
|
426
526
|
case 'iframe-ready':
|
|
427
|
-
|
|
527
|
+
this.messageBridge.sendAllData();
|
|
428
528
|
break;
|
|
429
529
|
|
|
430
530
|
case 'iframe-auth-status':
|
|
431
|
-
console.log('[Django-CFG] Auth status
|
|
531
|
+
console.log('[Django-CFG] Auth status:', data);
|
|
432
532
|
break;
|
|
433
533
|
|
|
434
534
|
case 'iframe-resize':
|
|
435
|
-
|
|
436
|
-
// if (data?.height && typeof data.height === 'number') {
|
|
437
|
-
// // First resize - show iframe immediately
|
|
438
|
-
// if (!iframe.classList.contains('loaded')) {
|
|
439
|
-
// console.log('[Django-CFG] First resize received - showing iframe');
|
|
440
|
-
// if (loading) loading.classList.add('hidden');
|
|
441
|
-
// iframe.classList.add('loaded');
|
|
442
|
-
// }
|
|
443
|
-
//
|
|
444
|
-
// // Debounce resize updates (300ms delay)
|
|
445
|
-
// if (resizeTimer) {
|
|
446
|
-
// clearTimeout(resizeTimer);
|
|
447
|
-
// }
|
|
448
|
-
//
|
|
449
|
-
// resizeTimer = setTimeout(() => {
|
|
450
|
-
// const newHeight = Math.max(600, data.height + 50);
|
|
451
|
-
// iframe.style.height = newHeight + 'px';
|
|
452
|
-
// }, 300);
|
|
453
|
-
// }
|
|
454
|
-
|
|
455
|
-
// Show iframe on first resize event
|
|
456
|
-
if (!iframe.classList.contains('loaded')) {
|
|
457
|
-
console.log('[Django-CFG] First resize received - showing iframe');
|
|
458
|
-
if (loading) loading.classList.add('hidden');
|
|
459
|
-
iframe.classList.add('loaded');
|
|
460
|
-
}
|
|
535
|
+
this._handleResize();
|
|
461
536
|
break;
|
|
462
537
|
|
|
463
538
|
case 'iframe-navigation':
|
|
464
|
-
|
|
465
|
-
// Track current path for "Open in New Window" button
|
|
466
|
-
if (iframe.id === 'nextjs-dashboard-iframe-nextjs' && data?.path) {
|
|
467
|
-
// Update Alpine.js data
|
|
468
|
-
const alpineEl = document.querySelector('[x-data]');
|
|
469
|
-
if (alpineEl && window.Alpine) {
|
|
470
|
-
const alpineData = window.Alpine.$data(alpineEl);
|
|
471
|
-
if (alpineData) {
|
|
472
|
-
alpineData.currentNextjsPath = data.path;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
}
|
|
539
|
+
this._handleNavigation(data?.path);
|
|
476
540
|
break;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
477
543
|
|
|
478
|
-
|
|
479
|
-
|
|
544
|
+
_handleResize() {
|
|
545
|
+
if (this.iframe.classList.contains('loaded')) return;
|
|
546
|
+
|
|
547
|
+
console.log('[Django-CFG] First resize - showing iframe');
|
|
548
|
+
this.loading?.classList.add('hidden');
|
|
549
|
+
this.iframe.classList.add('loaded');
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
_handleNavigation(path) {
|
|
553
|
+
if (!path) return;
|
|
554
|
+
|
|
555
|
+
console.log('[Django-CFG] Navigation:', path);
|
|
556
|
+
|
|
557
|
+
// Track path for "Open in New Window" (only for nextjs tab)
|
|
558
|
+
if (this.iframe.id === 'nextjs-dashboard-iframe-nextjs') {
|
|
559
|
+
this._updateAlpinePath(path);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
_updateAlpinePath(path) {
|
|
564
|
+
const alpineEl = document.querySelector('[x-data]');
|
|
565
|
+
if (!alpineEl || !window.Alpine) return;
|
|
566
|
+
|
|
567
|
+
const alpineData = window.Alpine.$data(alpineEl);
|
|
568
|
+
if (alpineData) {
|
|
569
|
+
alpineData.currentNextjsPath = path;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
destroy() {
|
|
574
|
+
this.messageBridge.destroy();
|
|
575
|
+
if (this.messageListener) {
|
|
576
|
+
window.removeEventListener('message', this.messageListener);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* ExternalIframeHandler - Handles external iframes (docs) without postMessage
|
|
583
|
+
*/
|
|
584
|
+
class ExternalIframeHandler {
|
|
585
|
+
constructor(iframe, loading) {
|
|
586
|
+
this.iframe = iframe;
|
|
587
|
+
this.loading = loading;
|
|
588
|
+
this.LOAD_TIMEOUT = 5000;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
init() {
|
|
592
|
+
this._setupLoadHandler();
|
|
593
|
+
this._setupErrorHandler();
|
|
594
|
+
this._setupTimeout();
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
_setupLoadHandler() {
|
|
598
|
+
this.iframe.addEventListener('load', () => {
|
|
599
|
+
console.log('[Django-CFG] External iframe loaded');
|
|
600
|
+
this._showIframe();
|
|
601
|
+
});
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
_setupErrorHandler() {
|
|
605
|
+
this.iframe.addEventListener('error', () => {
|
|
606
|
+
console.error('[Django-CFG] External iframe failed to load');
|
|
607
|
+
this._showError();
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
_setupTimeout() {
|
|
612
|
+
setTimeout(() => {
|
|
613
|
+
if (!this.iframe.classList.contains('loaded')) {
|
|
614
|
+
console.log('[Django-CFG] Timeout - showing iframe anyway');
|
|
615
|
+
this._showIframe();
|
|
616
|
+
}
|
|
617
|
+
}, this.LOAD_TIMEOUT);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
_showIframe() {
|
|
621
|
+
this.loading?.classList.add('hidden');
|
|
622
|
+
this.iframe.classList.add('loaded');
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
_showError() {
|
|
626
|
+
const loadingText = this.loading?.querySelector('p');
|
|
627
|
+
if (loadingText) {
|
|
628
|
+
loadingText.textContent = 'Failed to load. Please check your connection.';
|
|
480
629
|
}
|
|
481
|
-
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
destroy() {
|
|
633
|
+
// No cleanup needed for external iframes
|
|
634
|
+
}
|
|
482
635
|
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* IframeManager - Factory for creating appropriate iframe handlers
|
|
639
|
+
*/
|
|
640
|
+
class IframeManager {
|
|
641
|
+
constructor() {
|
|
642
|
+
this.handlers = [];
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Register an iframe for management
|
|
647
|
+
*/
|
|
648
|
+
register(iframeId, loadingId, options = {}) {
|
|
649
|
+
const iframe = document.getElementById(iframeId);
|
|
650
|
+
const loading = document.getElementById(loadingId);
|
|
651
|
+
|
|
652
|
+
if (!iframe) {
|
|
653
|
+
console.warn(`[Django-CFG] Iframe not found: ${iframeId}`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const handler = this._createHandler(iframe, loading, options);
|
|
658
|
+
if (handler) {
|
|
659
|
+
handler.init();
|
|
660
|
+
this.handlers.push(handler);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Create appropriate handler based on iframe type
|
|
666
|
+
*/
|
|
667
|
+
_createHandler(iframe, loading, options) {
|
|
668
|
+
const src = iframe.src || iframe.getAttribute('src');
|
|
669
|
+
if (!src) {
|
|
670
|
+
console.warn('[Django-CFG] Iframe has no src');
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
const url = new URL(src, window.location.origin);
|
|
675
|
+
const isExternal = options.external || url.origin !== window.location.origin;
|
|
676
|
+
|
|
677
|
+
if (isExternal) {
|
|
678
|
+
return new ExternalIframeHandler(iframe, loading);
|
|
679
|
+
} else {
|
|
680
|
+
return new InternalIframeHandler(iframe, loading, url.origin);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Cleanup all handlers
|
|
686
|
+
*/
|
|
687
|
+
destroy() {
|
|
688
|
+
this.handlers.forEach(handler => handler.destroy());
|
|
689
|
+
this.handlers = [];
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Initialize iframe manager
|
|
694
|
+
const manager = new IframeManager();
|
|
695
|
+
|
|
696
|
+
// Register all iframes
|
|
697
|
+
manager.register('nextjs-dashboard-iframe-builtin', 'iframe-loading-builtin');
|
|
698
|
+
manager.register('nextjs-dashboard-iframe-nextjs', 'iframe-loading-nextjs');
|
|
699
|
+
manager.register('nextjs-dashboard-iframe-docs', 'iframe-loading-docs', { external: true });
|
|
700
|
+
|
|
701
|
+
// Cleanup on page unload
|
|
702
|
+
window.addEventListener('beforeunload', () => {
|
|
703
|
+
manager.destroy();
|
|
704
|
+
});
|
|
705
|
+
|
|
483
706
|
})();
|
|
484
707
|
</script>
|
|
485
708
|
{% endblock %}
|