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.

Files changed (177) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/permissions.py +48 -0
  4. django_cfg/apps/dashboard/serializers/__init__.py +8 -1
  5. django_cfg/apps/dashboard/serializers/commands.py +29 -0
  6. django_cfg/apps/dashboard/services/__init__.py +2 -0
  7. django_cfg/{modules/django_unfold/callbacks/base.py → apps/dashboard/services/commands_security.py} +28 -90
  8. django_cfg/apps/dashboard/services/commands_service.py +208 -9
  9. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  10. django_cfg/apps/dashboard/views/commands_views.py +92 -4
  11. django_cfg/apps/frontend/test_routing.py +134 -0
  12. django_cfg/apps/frontend/views.py +73 -28
  13. django_cfg/apps/urls.py +0 -1
  14. django_cfg/core/builders/apps_builder.py +0 -58
  15. django_cfg/modules/django_unfold/__init__.py +5 -24
  16. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  17. django_cfg/modules/django_unfold/models/config.py +11 -65
  18. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  19. django_cfg/modules/django_unfold/tailwind.py +2 -4
  20. django_cfg/pyproject.toml +1 -1
  21. django_cfg/registry/third_party.py +0 -9
  22. django_cfg/routing/callbacks.py +1 -43
  23. django_cfg/static/frontend/admin/404/index.html +1 -1
  24. django_cfg/static/frontend/admin/404.html +1 -1
  25. django_cfg/static/frontend/admin/500/index.html +1 -1
  26. django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  27. django_cfg/static/frontend/admin/_next/static/chunks/{19430.fe7bff7372f8a256.js → 19430.c4c95603c23c17fe.js} +1 -1
  28. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  29. django_cfg/static/frontend/admin/_next/static/chunks/94141-bc6d47f419b26b21.js +1 -0
  30. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-c336f254967dd101.js → _app-c7dcd3aa616fab68.js} +6 -6
  31. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-b39c7f22c066e2c6.js → cookies-97d279800f12aab4.js} +1 -1
  32. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-5aedad0cf3a4f80f.js → privacy-1d5e6cd94689247e.js} +1 -1
  33. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-dbd854d0d5d483e2.js → security-55e49700e7a01f5a.js} +1 -1
  34. django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-f3e1d2b9e5edf12f.js → terms-14c02bb2d3198352.js} +1 -1
  35. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-22532c65971225eb.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  36. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-d4ccbe1265cbd853.js +1 -0
  37. django_cfg/static/frontend/admin/_next/static/chunks/{webpack-da114020a6b940f5.js → webpack-5a92f81363b62aa7.js} +1 -1
  38. django_cfg/static/frontend/admin/_next/static/css/3063068f0d5a8a00.css +3 -0
  39. django_cfg/static/frontend/admin/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
  40. django_cfg/static/frontend/admin/_next/static/media/21350d82a1f187e9-s.p.woff2 +0 -0
  41. django_cfg/static/frontend/admin/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
  42. django_cfg/static/frontend/admin/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
  43. django_cfg/static/frontend/admin/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
  44. django_cfg/static/frontend/admin/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
  45. django_cfg/static/frontend/admin/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
  46. django_cfg/static/frontend/admin/auth/index.html +1 -1
  47. django_cfg/static/frontend/admin/index.html +1 -1
  48. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  49. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  50. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  51. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  52. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  53. django_cfg/static/frontend/admin/private/index.html +1 -1
  54. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  55. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  56. django_cfg/templates/admin/index.html +1 -1
  57. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  58. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/RECORD +62 -163
  59. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  60. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  61. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  62. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  63. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  64. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  65. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  66. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  67. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  68. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  69. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  70. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  71. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  72. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  73. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  74. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  75. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  76. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  77. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  78. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  79. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  80. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  81. django_cfg/modules/django_dashboard/__init__.py +0 -23
  82. django_cfg/modules/django_dashboard/components.py +0 -312
  83. django_cfg/modules/django_dashboard/debug.py +0 -174
  84. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  85. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  86. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  87. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  88. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  89. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  90. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  91. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  92. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  93. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  94. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  95. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  96. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  97. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  98. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  99. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  100. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  101. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  102. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  103. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  104. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  105. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  106. django_cfg/modules/django_unfold/models.py +0 -98
  107. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  108. django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +0 -1
  109. django_cfg/static/frontend/admin/_next/static/chunks/50314-48bd5701f62faf27.js +0 -1
  110. django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +0 -1
  111. django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +0 -3
  112. django_cfg/static/frontend/admin/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
  113. django_cfg/static/frontend/admin/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
  114. django_cfg/static/frontend/admin/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
  115. django_cfg/static/frontend/admin/_next/static/media/875ae681bfde4580-s.p.woff2 +0 -0
  116. django_cfg/static/frontend/admin/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
  117. django_cfg/static/frontend/admin/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
  118. django_cfg/templates/admin/sections/commands_section.html +0 -5
  119. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  120. django_cfg/templates/admin/sections/overview_section.html +0 -5
  121. django_cfg/templates/admin/sections/stats_section.html +0 -5
  122. django_cfg/templates/admin/sections/system_section.html +0 -5
  123. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  124. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  125. django_cfg/templates/admin_old/components/card.html +0 -50
  126. django_cfg/templates/admin_old/components/data_table.html +0 -67
  127. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  128. django_cfg/templates/admin_old/components/modal.html +0 -58
  129. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  130. django_cfg/templates/admin_old/components/section_header.html +0 -26
  131. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  132. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  133. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  134. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  135. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  136. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  137. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  138. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  139. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  140. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  141. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  142. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  143. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  144. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  145. django_cfg/templates/admin_old/index.html +0 -80
  146. django_cfg/templates/admin_old/index_new.html +0 -119
  147. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  148. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  149. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  150. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  151. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  152. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  153. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  154. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  155. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  156. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  157. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  158. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  159. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  160. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  161. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  162. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  163. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  164. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  165. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  166. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  167. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  168. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  169. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  170. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  171. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  172. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  173. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  174. /django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  175. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  176. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  177. {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -1,803 +0,0 @@
1
- /**
2
- * Live Testing Module
3
- * Interactive WebSocket testing client for Centrifugo integration
4
- */
5
-
6
- export class LiveTestingModule {
7
- constructor(api, dashboard) {
8
- this.api = api;
9
- this.dashboard = dashboard;
10
- this.centrifuge = null;
11
- this.subscriptions = new Map(); // channel -> subscription object
12
- this.receivedMessages = [];
13
- this.sentAcks = [];
14
- this.eventsLog = []; // WebSocket events log
15
- this.autoAck = true;
16
- this.clientId = this.generateClientId();
17
- this.connectionToken = null;
18
- }
19
-
20
- /**
21
- * Generate unique client ID for this session
22
- */
23
- generateClientId() {
24
- const stored = localStorage.getItem('centrifugo_client_id');
25
- if (stored) return stored;
26
-
27
- const id = 'client_' + Math.random().toString(36).substr(2, 9);
28
- localStorage.setItem('centrifugo_client_id', id);
29
- return id;
30
- }
31
-
32
- /**
33
- * Log WebSocket event
34
- * @param {string} type - Event type: connection, subscription, publication, error, ack
35
- * @param {string} message - Human-readable message
36
- * @param {object} data - Additional event data
37
- */
38
- logEvent(type, message, data = null) {
39
- const event = {
40
- type,
41
- message,
42
- data,
43
- timestamp: new Date().toISOString()
44
- };
45
-
46
- this.eventsLog.unshift(event);
47
-
48
- // Limit to 100 events
49
- if (this.eventsLog.length > 100) {
50
- this.eventsLog = this.eventsLog.slice(0, 100);
51
- }
52
-
53
- console.log(`[${type.toUpperCase()}]`, message, data || '');
54
- this.renderEventsLog();
55
- }
56
-
57
- /**
58
- * Connect to Centrifugo WebSocket
59
- */
60
- async connectToCentrifugo(userId) {
61
- try {
62
- console.log('Requesting connection token...');
63
- this.logEvent('connection', 'Requesting connection token...', { user_id: userId });
64
-
65
- // Get JWT token from backend
66
- const tokenResponse = await this.api.centrifugoAdminApiTestingConnectionTokenCreate({
67
- user_id: userId || 'test-user',
68
- channels: []
69
- });
70
-
71
- if (!tokenResponse || !tokenResponse.token) {
72
- throw new Error('Failed to get connection token');
73
- }
74
-
75
- this.connectionToken = tokenResponse.token;
76
- const wsUrl = tokenResponse.centrifugo_url;
77
-
78
- console.log('Connecting to Centrifugo:', wsUrl);
79
- this.logEvent('connection', 'Connecting to Centrifugo...', { url: wsUrl });
80
-
81
- // Initialize Centrifuge client
82
- this.centrifuge = new Centrifuge(wsUrl, {
83
- token: this.connectionToken,
84
- debug: true
85
- });
86
-
87
- // Setup event handlers
88
- this.centrifuge.on('connected', (ctx) => {
89
- console.log('Connected to Centrifugo!', ctx);
90
- this.onConnected(ctx);
91
- });
92
-
93
- this.centrifuge.on('disconnected', (ctx) => {
94
- console.log('Disconnected from Centrifugo', ctx);
95
- this.onDisconnected(ctx);
96
- });
97
-
98
- this.centrifuge.on('error', (ctx) => {
99
- console.error('Centrifugo error:', ctx);
100
- this.onError(ctx);
101
- });
102
-
103
- // Connect
104
- this.centrifuge.connect();
105
-
106
- return { success: true };
107
-
108
- } catch (error) {
109
- console.error('Failed to connect to Centrifugo:', error);
110
- this.logEvent('error', 'Connection failed: ' + error.message, { error: error.message });
111
- return { success: false, error: error.message };
112
- }
113
- }
114
-
115
- /**
116
- * Disconnect from Centrifugo
117
- */
118
- disconnect() {
119
- if (this.centrifuge) {
120
- this.centrifuge.disconnect();
121
- this.centrifuge = null;
122
- }
123
- this.subscriptions.clear();
124
- this.updateConnectionStatus(false);
125
- }
126
-
127
- /**
128
- * Subscribe to channel
129
- */
130
- subscribeToChannel(channel) {
131
- if (!this.centrifuge) {
132
- alert('Connect to Centrifugo first!');
133
- return;
134
- }
135
-
136
- if (this.subscriptions.has(channel)) {
137
- alert(`Already subscribed to ${channel}`);
138
- return;
139
- }
140
-
141
- console.log('Subscribing to channel:', channel);
142
-
143
- const subscription = this.centrifuge.newSubscription(channel);
144
-
145
- // Handle publications
146
- subscription.on('publication', async (ctx) => {
147
- console.log('Received publication:', ctx.data);
148
- await this.onPublication(channel, ctx.data);
149
- });
150
-
151
- subscription.on('subscribed', (ctx) => {
152
- console.log(`Subscribed to ${channel}`, ctx);
153
- this.onSubscribed(channel);
154
- });
155
-
156
- subscription.on('unsubscribed', (ctx) => {
157
- console.log(`Unsubscribed from ${channel}`, ctx);
158
- this.onUnsubscribed(channel);
159
- });
160
-
161
- subscription.subscribe();
162
- this.subscriptions.set(channel, subscription);
163
- }
164
-
165
- /**
166
- * Unsubscribe from channel
167
- */
168
- unsubscribeFromChannel(channel) {
169
- const subscription = this.subscriptions.get(channel);
170
- if (subscription) {
171
- subscription.unsubscribe();
172
- this.subscriptions.delete(channel);
173
- }
174
- }
175
-
176
- /**
177
- * Publish test message via wrapper
178
- */
179
- async publishTestMessage(channel, data, waitForAck = false, ackTimeout = 10) {
180
- try {
181
- console.log('Publishing test message...', { channel, data, waitForAck });
182
-
183
- const response = await this.api.centrifugoAdminApiTestingPublishTestCreate({
184
- channel,
185
- data,
186
- wait_for_ack: waitForAck,
187
- ack_timeout: ackTimeout
188
- });
189
-
190
- console.log('Publish response:', response);
191
-
192
- return response;
193
-
194
- } catch (error) {
195
- console.error('Failed to publish test message:', error);
196
- return {
197
- success: false,
198
- error: error.message
199
- };
200
- }
201
- }
202
-
203
- /**
204
- * Send manual ACK for message
205
- */
206
- async sendManualAck(messageId) {
207
- try {
208
- console.log('Sending manual ACK for message:', messageId);
209
- this.logEvent('ack', `Sending ACK for message ${messageId}`, { message_id: messageId });
210
-
211
- const response = await this.api.centrifugoAdminApiTestingSendAckCreate({
212
- message_id: messageId,
213
- client_id: this.clientId
214
- });
215
-
216
- console.log('ACK response:', response);
217
-
218
- if (response.success) {
219
- this.logEvent('ack', `ACK sent successfully for ${messageId}`, { message_id: messageId });
220
- } else {
221
- this.logEvent('error', `ACK failed for ${messageId}`, { message_id: messageId, error: response.error });
222
- }
223
-
224
- // Track sent ACK
225
- this.sentAcks.push({
226
- message_id: messageId,
227
- timestamp: new Date().toISOString(),
228
- success: response.success
229
- });
230
-
231
- this.renderAckHistory();
232
-
233
- return response;
234
-
235
- } catch (error) {
236
- console.error('Failed to send ACK:', error);
237
- this.logEvent('error', `ACK request failed: ${error.message}`, { message_id: messageId, error: error.message });
238
- return {
239
- success: false,
240
- error: error.message
241
- };
242
- }
243
- }
244
-
245
- /**
246
- * Handle connection established
247
- */
248
- onConnected(ctx) {
249
- this.logEvent('connection', 'Connected to Centrifugo', { client_id: ctx.client });
250
- this.updateConnectionStatus(true);
251
- this.updateElement('ws-client-id', ctx.client);
252
-
253
- // Show success notification
254
- if (window.showNotification) {
255
- window.showNotification('Connected to Centrifugo!', 'success');
256
- }
257
- }
258
-
259
- /**
260
- * Handle disconnection
261
- */
262
- onDisconnected(ctx) {
263
- this.logEvent('connection', 'Disconnected from Centrifugo', { reason: ctx.reason, code: ctx.code });
264
- this.updateConnectionStatus(false);
265
-
266
- if (window.showNotification) {
267
- window.showNotification('Disconnected from Centrifugo', 'warning');
268
- }
269
- }
270
-
271
- /**
272
- * Handle connection error
273
- */
274
- onError(ctx) {
275
- console.error('Connection error:', ctx);
276
- this.logEvent('error', 'WebSocket error: ' + ctx.message, { error: ctx });
277
-
278
- if (window.showNotification) {
279
- window.showNotification('Centrifugo connection error: ' + ctx.message, 'error');
280
- }
281
- }
282
-
283
- /**
284
- * Handle incoming publication
285
- */
286
- async onPublication(channel, data) {
287
- this.logEvent('publication', `Message received on ${channel}`, {
288
- channel,
289
- message_id: data._message_id,
290
- ack_required: data._ack_required
291
- });
292
-
293
- // Add to received messages
294
- this.receivedMessages.unshift({
295
- channel,
296
- data,
297
- timestamp: new Date().toISOString(),
298
- ack_required: data._ack_required || false,
299
- message_id: data._message_id || null,
300
- ack_sent: false
301
- });
302
-
303
- // Limit history to 50 messages
304
- if (this.receivedMessages.length > 50) {
305
- this.receivedMessages = this.receivedMessages.slice(0, 50);
306
- }
307
-
308
- // Render message in UI
309
- this.renderReceivedMessages();
310
-
311
- // Auto-send ACK if required and enabled
312
- if (this.autoAck && data._ack_required && data._message_id) {
313
- console.log('Auto-sending ACK for message:', data._message_id);
314
- await this.sendManualAck(data._message_id);
315
-
316
- // Mark message as ACK sent
317
- const msg = this.receivedMessages.find(m => m.message_id === data._message_id);
318
- if (msg) {
319
- msg.ack_sent = true;
320
- this.renderReceivedMessages();
321
- }
322
- }
323
- }
324
-
325
- /**
326
- * Handle channel subscription success
327
- */
328
- onSubscribed(channel) {
329
- this.logEvent('subscription', `Subscribed to ${channel}`, { channel });
330
- this.renderSubscriptionsList();
331
-
332
- if (window.showNotification) {
333
- window.showNotification(`Subscribed to ${channel}`, 'success');
334
- }
335
- }
336
-
337
- /**
338
- * Handle channel unsubscription
339
- */
340
- onUnsubscribed(channel) {
341
- this.logEvent('subscription', `Unsubscribed from ${channel}`, { channel });
342
- this.renderSubscriptionsList();
343
-
344
- if (window.showNotification) {
345
- window.showNotification(`Unsubscribed from ${channel}`, 'info');
346
- }
347
- }
348
-
349
- /**
350
- * Update connection status indicator
351
- */
352
- updateConnectionStatus(connected) {
353
- const statusEl = document.getElementById('ws-connection-status');
354
- const connectBtn = document.getElementById('ws-connect-btn');
355
- const disconnectBtn = document.getElementById('ws-disconnect-btn');
356
- const subscribeBtn = document.getElementById('ws-subscribe-btn');
357
-
358
- if (statusEl) {
359
- if (connected) {
360
- statusEl.textContent = 'Connected';
361
- statusEl.className = 'px-3 py-1 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded-full text-sm font-medium';
362
- } else {
363
- statusEl.textContent = 'Disconnected';
364
- statusEl.className = 'px-3 py-1 bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200 rounded-full text-sm font-medium';
365
- }
366
- }
367
-
368
- if (connectBtn) connectBtn.disabled = connected;
369
- if (disconnectBtn) disconnectBtn.disabled = !connected;
370
- if (subscribeBtn) subscribeBtn.disabled = !connected;
371
- }
372
-
373
- /**
374
- * Render subscriptions list
375
- */
376
- renderSubscriptionsList() {
377
- const container = document.getElementById('ws-subscriptions-list');
378
- if (!container) return;
379
-
380
- if (this.subscriptions.size === 0) {
381
- container.innerHTML = `
382
- <div class="text-center py-4 text-gray-500 dark:text-gray-400 text-sm">
383
- No active subscriptions
384
- </div>
385
- `;
386
- return;
387
- }
388
-
389
- let html = '<div class="space-y-2">';
390
- this.subscriptions.forEach((sub, channel) => {
391
- html += `
392
- <div class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 rounded-lg p-3">
393
- <div class="flex items-center gap-2">
394
- <span class="material-icons text-purple-500 text-sm">radio_button_checked</span>
395
- <code class="text-sm font-mono text-gray-900 dark:text-white">${this.escapeHtml(channel)}</code>
396
- </div>
397
- <button onclick="window.centrifugoDashboard.liveTestingModule.unsubscribeFromChannel('${this.escapeHtml(channel)}')"
398
- class="px-3 py-1 bg-red-500 hover:bg-red-600 text-white rounded text-xs font-medium">
399
- Unsubscribe
400
- </button>
401
- </div>
402
- `;
403
- });
404
- html += '</div>';
405
-
406
- container.innerHTML = html;
407
-
408
- // Update count badge
409
- const badge = document.getElementById('ws-subscriptions-count');
410
- if (badge) {
411
- badge.textContent = this.subscriptions.size;
412
- }
413
- }
414
-
415
- /**
416
- * Render received messages
417
- */
418
- renderReceivedMessages() {
419
- const container = document.getElementById('ws-received-messages');
420
- if (!container) return;
421
-
422
- if (this.receivedMessages.length === 0) {
423
- container.innerHTML = `
424
- <div class="text-center py-8 text-gray-500 dark:text-gray-400">
425
- No messages received yet...
426
- </div>
427
- `;
428
- return;
429
- }
430
-
431
- let html = '<div class="space-y-3">';
432
- this.receivedMessages.forEach(msg => {
433
- const timeStr = new Date(msg.timestamp).toLocaleTimeString();
434
- const ackBadge = msg.ack_required ?
435
- (msg.ack_sent ?
436
- '<span class="px-2 py-1 bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 rounded text-xs">ACK Sent</span>' :
437
- '<span class="px-2 py-1 bg-yellow-100 dark:bg-yellow-900 text-yellow-800 dark:text-yellow-200 rounded text-xs">ACK Required</span>'
438
- ) : '';
439
-
440
- html += `
441
- <div class="bg-white dark:bg-gray-700 rounded-lg p-4 border border-gray-200 dark:border-gray-600">
442
- <div class="flex items-start justify-between mb-2">
443
- <div class="flex-1">
444
- <div class="flex items-center gap-2 mb-1">
445
- <span class="text-xs font-medium text-gray-600 dark:text-gray-400">${timeStr}</span>
446
- <code class="text-xs bg-purple-100 dark:bg-purple-900 text-purple-800 dark:text-purple-200 px-2 py-0.5 rounded">${this.escapeHtml(msg.channel)}</code>
447
- ${ackBadge}
448
- </div>
449
- </div>
450
- ${msg.ack_required && !msg.ack_sent && msg.message_id ? `
451
- <button onclick="window.centrifugoDashboard.liveTestingModule.sendManualAck('${msg.message_id}')"
452
- class="px-3 py-1 bg-blue-500 hover:bg-blue-600 text-white rounded text-xs font-medium flex items-center gap-1">
453
- <span class="material-icons text-sm">check</span>
454
- Send ACK
455
- </button>
456
- ` : ''}
457
- </div>
458
- <pre class="text-xs bg-gray-50 dark:bg-gray-800 p-3 rounded overflow-x-auto"><code>${this.escapeHtml(JSON.stringify(msg.data, null, 2))}</code></pre>
459
- </div>
460
- `;
461
- });
462
- html += '</div>';
463
-
464
- container.innerHTML = html;
465
-
466
- // Update count
467
- const countEl = document.getElementById('ws-messages-count');
468
- if (countEl) {
469
- countEl.textContent = this.receivedMessages.length;
470
- }
471
- }
472
-
473
- /**
474
- * Render ACK history
475
- */
476
- renderAckHistory() {
477
- const container = document.getElementById('ws-ack-history');
478
- if (!container) return;
479
-
480
- if (this.sentAcks.length === 0) {
481
- container.innerHTML = `
482
- <div class="text-center py-4 text-gray-500 dark:text-gray-400 text-sm">
483
- No ACKs sent yet
484
- </div>
485
- `;
486
- return;
487
- }
488
-
489
- let html = '<div class="space-y-2">';
490
- this.sentAcks.slice().reverse().slice(0, 10).forEach(ack => {
491
- const timeStr = new Date(ack.timestamp).toLocaleTimeString();
492
- const statusIcon = ack.success ?
493
- '<span class="material-icons text-green-500 text-sm">check_circle</span>' :
494
- '<span class="material-icons text-red-500 text-sm">error</span>';
495
-
496
- html += `
497
- <div class="flex items-center justify-between bg-gray-50 dark:bg-gray-700 rounded p-2">
498
- <div class="flex items-center gap-2">
499
- ${statusIcon}
500
- <code class="text-xs font-mono text-gray-900 dark:text-white">${this.escapeHtml(ack.message_id.substring(0, 12))}...</code>
501
- </div>
502
- <span class="text-xs text-gray-500 dark:text-gray-400">${timeStr}</span>
503
- </div>
504
- `;
505
- });
506
- html += '</div>';
507
-
508
- container.innerHTML = html;
509
- }
510
-
511
- /**
512
- * Render events log
513
- */
514
- renderEventsLog() {
515
- const container = document.getElementById('ws-events-log');
516
- if (!container) return;
517
-
518
- if (this.eventsLog.length === 0) {
519
- container.innerHTML = `
520
- <div class="text-center py-4 text-gray-500 dark:text-gray-400 text-sm">
521
- No events logged yet
522
- </div>
523
- `;
524
- return;
525
- }
526
-
527
- let html = '<div class="space-y-1 max-h-[400px] overflow-y-auto">';
528
- this.eventsLog.forEach(event => {
529
- const timeStr = new Date(event.timestamp).toLocaleTimeString();
530
-
531
- // Color coding by event type
532
- let colorClass = 'text-gray-600 dark:text-gray-400';
533
- let iconName = 'info';
534
- let bgClass = 'bg-gray-50 dark:bg-gray-700';
535
-
536
- switch (event.type) {
537
- case 'connection':
538
- colorClass = 'text-green-600 dark:text-green-400';
539
- iconName = 'link';
540
- bgClass = 'bg-green-50 dark:bg-green-900/20';
541
- break;
542
- case 'subscription':
543
- colorClass = 'text-blue-600 dark:text-blue-400';
544
- iconName = 'radio_button_checked';
545
- bgClass = 'bg-blue-50 dark:bg-blue-900/20';
546
- break;
547
- case 'publication':
548
- colorClass = 'text-purple-600 dark:text-purple-400';
549
- iconName = 'message';
550
- bgClass = 'bg-purple-50 dark:bg-purple-900/20';
551
- break;
552
- case 'ack':
553
- colorClass = 'text-indigo-600 dark:text-indigo-400';
554
- iconName = 'check_circle';
555
- bgClass = 'bg-indigo-50 dark:bg-indigo-900/20';
556
- break;
557
- case 'error':
558
- colorClass = 'text-red-600 dark:text-red-400';
559
- iconName = 'error';
560
- bgClass = 'bg-red-50 dark:bg-red-900/20';
561
- break;
562
- }
563
-
564
- html += `
565
- <div class="${bgClass} rounded p-2 hover:shadow-sm transition-shadow">
566
- <div class="flex items-start gap-2">
567
- <span class="material-icons text-sm ${colorClass} flex-shrink-0 mt-0.5">${iconName}</span>
568
- <div class="flex-1 min-w-0">
569
- <div class="flex items-center gap-2 mb-0.5">
570
- <span class="text-xs font-medium ${colorClass} uppercase">${event.type}</span>
571
- <span class="text-xs text-gray-500 dark:text-gray-400">${timeStr}</span>
572
- </div>
573
- <p class="text-xs text-gray-700 dark:text-gray-300">${this.escapeHtml(event.message)}</p>
574
- ${event.data ? `
575
- <details class="mt-1">
576
- <summary class="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200">
577
- Details
578
- </summary>
579
- <pre class="text-xs bg-white dark:bg-gray-800 p-2 rounded mt-1 overflow-x-auto"><code>${this.escapeHtml(JSON.stringify(event.data, null, 2))}</code></pre>
580
- </details>
581
- ` : ''}
582
- </div>
583
- </div>
584
- </div>
585
- `;
586
- });
587
- html += '</div>';
588
-
589
- container.innerHTML = html;
590
-
591
- // Update count badge
592
- const badge = document.getElementById('ws-events-count');
593
- if (badge) {
594
- badge.textContent = this.eventsLog.length;
595
- }
596
- }
597
-
598
- /**
599
- * Clear events log
600
- */
601
- clearEventsLog() {
602
- this.eventsLog = [];
603
- this.renderEventsLog();
604
- this.logEvent('connection', 'Events log cleared');
605
- }
606
-
607
- /**
608
- * Export events log to JSON
609
- */
610
- exportEventsLog() {
611
- const dataStr = JSON.stringify(this.eventsLog, null, 2);
612
- const dataBlob = new Blob([dataStr], { type: 'application/json' });
613
- const url = URL.createObjectURL(dataBlob);
614
- const link = document.createElement('a');
615
- link.href = url;
616
- link.download = `centrifugo-events-${new Date().toISOString()}.json`;
617
- link.click();
618
- URL.revokeObjectURL(url);
619
-
620
- this.logEvent('connection', 'Events log exported to JSON');
621
- }
622
-
623
- /**
624
- * Clear received messages
625
- */
626
- clearReceivedMessages() {
627
- this.receivedMessages = [];
628
- this.renderReceivedMessages();
629
- }
630
-
631
- /**
632
- * Clear ACK history
633
- */
634
- clearAckHistory() {
635
- this.sentAcks = [];
636
- this.renderAckHistory();
637
- }
638
-
639
- /**
640
- * Toggle auto ACK
641
- */
642
- toggleAutoAck(enabled) {
643
- this.autoAck = enabled;
644
- console.log('Auto ACK:', this.autoAck ? 'enabled' : 'disabled');
645
- }
646
-
647
- /**
648
- * Run quick test scenario
649
- */
650
- async runQuickScenario(scenarioName) {
651
- console.log('Running quick scenario:', scenarioName);
652
-
653
- const scenarios = {
654
- 'simple-notification': {
655
- name: 'Simple Notification',
656
- channel: 'user#test-123',
657
- data: {
658
- type: 'notification',
659
- title: 'Test Notification',
660
- message: 'This is a simple test notification',
661
- timestamp: new Date().toISOString()
662
- },
663
- waitForAck: false,
664
- autoConnect: true,
665
- autoSubscribe: true
666
- },
667
- 'ack-notification': {
668
- name: 'ACK Required Notification',
669
- channel: 'user#test-123',
670
- data: {
671
- type: 'important',
672
- title: 'Important Message',
673
- message: 'Please acknowledge this message',
674
- priority: 'high',
675
- timestamp: new Date().toISOString()
676
- },
677
- waitForAck: true,
678
- ackTimeout: 10,
679
- autoConnect: true,
680
- autoSubscribe: true
681
- },
682
- 'broadcast': {
683
- name: 'Team Broadcast',
684
- channel: 'team#developers',
685
- data: {
686
- type: 'broadcast',
687
- title: 'Team Announcement',
688
- message: 'Testing broadcast to all team members',
689
- from: 'admin',
690
- timestamp: new Date().toISOString()
691
- },
692
- waitForAck: false,
693
- autoConnect: true,
694
- autoSubscribe: true
695
- },
696
- 'ack-flow': {
697
- name: 'Complete ACK Flow Test',
698
- channel: 'test#ack-flow',
699
- data: {
700
- type: 'test',
701
- title: 'ACK Flow Test',
702
- message: 'Testing complete publish → receive → ACK flow',
703
- test_id: Math.random().toString(36).substr(2, 9),
704
- timestamp: new Date().toISOString()
705
- },
706
- waitForAck: true,
707
- ackTimeout: 15,
708
- autoConnect: true,
709
- autoSubscribe: true
710
- }
711
- };
712
-
713
- const scenario = scenarios[scenarioName];
714
- if (!scenario) {
715
- alert('Unknown scenario: ' + scenarioName);
716
- return;
717
- }
718
-
719
- try {
720
- // Step 1: Connect if needed
721
- if (scenario.autoConnect && !this.centrifuge) {
722
- if (window.showNotification) {
723
- window.showNotification('Connecting to Centrifugo...', 'info');
724
- }
725
- const connectResult = await this.connectToCentrifugo('test-user-123');
726
- if (!connectResult.success) {
727
- throw new Error('Failed to connect: ' + connectResult.error);
728
- }
729
- // Wait for connection
730
- await new Promise(resolve => setTimeout(resolve, 1000));
731
- }
732
-
733
- // Step 2: Subscribe if needed
734
- if (scenario.autoSubscribe && !this.subscriptions.has(scenario.channel)) {
735
- if (window.showNotification) {
736
- window.showNotification('Subscribing to ' + scenario.channel + '...', 'info');
737
- }
738
- this.subscribeToChannel(scenario.channel);
739
- // Wait for subscription
740
- await new Promise(resolve => setTimeout(resolve, 500));
741
- }
742
-
743
- // Step 3: Publish message
744
- if (window.showNotification) {
745
- window.showNotification('Publishing test message...', 'info');
746
- }
747
-
748
- const result = await this.publishTestMessage(
749
- scenario.channel,
750
- scenario.data,
751
- scenario.waitForAck || false,
752
- scenario.ackTimeout || 10
753
- );
754
-
755
- // Step 4: Show result
756
- if (result.success) {
757
- let message = `✅ Scenario "${scenario.name}" completed!\n\n`;
758
- message += `Message ID: ${result.message_id}\n`;
759
- message += `Channel: ${result.channel}\n`;
760
- if (scenario.waitForAck) {
761
- message += `ACKs Received: ${result.acks_received}\n`;
762
- message += `Delivered: ${result.delivered ? 'Yes' : 'No'}`;
763
- }
764
-
765
- alert(message);
766
-
767
- if (window.showNotification) {
768
- window.showNotification('Scenario completed successfully!', 'success');
769
- }
770
- } else {
771
- throw new Error(result.error || 'Unknown error');
772
- }
773
-
774
- } catch (error) {
775
- console.error('Scenario failed:', error);
776
- alert('❌ Scenario failed: ' + error.message);
777
-
778
- if (window.showNotification) {
779
- window.showNotification('Scenario failed: ' + error.message, 'error');
780
- }
781
- }
782
- }
783
-
784
- /**
785
- * Update element text content
786
- */
787
- updateElement(id, value) {
788
- const element = document.getElementById(id);
789
- if (element) {
790
- element.textContent = value;
791
- }
792
- }
793
-
794
- /**
795
- * Escape HTML for safe rendering
796
- */
797
- escapeHtml(unsafe) {
798
- if (typeof unsafe !== 'string') return unsafe;
799
- const div = document.createElement('div');
800
- div.textContent = unsafe;
801
- return div.innerHTML;
802
- }
803
- }