django-cfg 1.4.88__py3-none-any.whl → 1.4.89__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of django-cfg might be problematic. Click here for more details.

Files changed (146) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/centrifugo/views/__init__.py +0 -2
  3. django_cfg/apps/dashboard/services/__init__.py +2 -0
  4. django_cfg/apps/dashboard/services/overview_service.py +205 -0
  5. django_cfg/apps/frontend/test_routing.py +134 -0
  6. django_cfg/apps/frontend/views.py +69 -28
  7. django_cfg/apps/urls.py +0 -1
  8. django_cfg/core/builders/apps_builder.py +0 -58
  9. django_cfg/modules/django_unfold/__init__.py +5 -24
  10. django_cfg/modules/django_unfold/models/__init__.py +0 -23
  11. django_cfg/modules/django_unfold/models/config.py +11 -65
  12. django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
  13. django_cfg/modules/django_unfold/tailwind.py +2 -4
  14. django_cfg/pyproject.toml +1 -1
  15. django_cfg/registry/third_party.py +0 -9
  16. django_cfg/routing/callbacks.py +1 -43
  17. django_cfg/static/frontend/admin/404/index.html +1 -1
  18. django_cfg/static/frontend/admin/404.html +1 -1
  19. django_cfg/static/frontend/admin/500/index.html +1 -1
  20. django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
  21. django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
  22. django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-1c0fff0f59a6d683.js → _app-c7dcd3aa616fab68.js} +1 -1
  23. django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-44a8313fa040e9ad.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
  24. django_cfg/static/frontend/admin/auth/index.html +1 -1
  25. django_cfg/static/frontend/admin/index.html +1 -1
  26. django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
  27. django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
  28. django_cfg/static/frontend/admin/legal/security/index.html +1 -1
  29. django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
  30. django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
  31. django_cfg/static/frontend/admin/private/index.html +1 -1
  32. django_cfg/static/frontend/admin/private/profile/index.html +1 -1
  33. django_cfg/static/frontend/admin/private/ui/index.html +2 -2
  34. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
  35. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/RECORD +39 -143
  36. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
  37. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
  38. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
  39. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
  40. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
  41. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
  42. django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
  43. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
  44. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
  45. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
  46. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
  47. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
  48. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
  49. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
  50. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
  51. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
  52. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
  53. django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
  54. django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
  55. django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
  56. django_cfg/apps/centrifugo/urls_admin.py +0 -20
  57. django_cfg/apps/centrifugo/views/dashboard.py +0 -28
  58. django_cfg/modules/django_dashboard/__init__.py +0 -23
  59. django_cfg/modules/django_dashboard/components.py +0 -312
  60. django_cfg/modules/django_dashboard/debug.py +0 -174
  61. django_cfg/modules/django_dashboard/management/__init__.py +0 -0
  62. django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
  63. django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
  64. django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
  65. django_cfg/modules/django_dashboard/sections/base.py +0 -129
  66. django_cfg/modules/django_dashboard/sections/commands.py +0 -33
  67. django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
  68. django_cfg/modules/django_dashboard/sections/overview.py +0 -398
  69. django_cfg/modules/django_dashboard/sections/stats.py +0 -48
  70. django_cfg/modules/django_dashboard/sections/system.py +0 -74
  71. django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
  72. django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
  73. django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
  74. django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
  75. django_cfg/modules/django_unfold/callbacks/base.py +0 -290
  76. django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
  77. django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
  78. django_cfg/modules/django_unfold/callbacks/main.py +0 -322
  79. django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
  80. django_cfg/modules/django_unfold/callbacks/system.py +0 -180
  81. django_cfg/modules/django_unfold/callbacks/users.py +0 -65
  82. django_cfg/modules/django_unfold/models/dashboard.py +0 -207
  83. django_cfg/modules/django_unfold/models/tabs.py +0 -26
  84. django_cfg/modules/django_unfold/models.py +0 -98
  85. django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
  86. django_cfg/static/frontend/admin/_next/static/chunks/50314-5ec79b293c2283dd.js +0 -1
  87. django_cfg/templates/admin/sections/commands_section.html +0 -5
  88. django_cfg/templates/admin/sections/documentation_section.html +0 -5
  89. django_cfg/templates/admin/sections/overview_section.html +0 -5
  90. django_cfg/templates/admin/sections/stats_section.html +0 -5
  91. django_cfg/templates/admin/sections/system_section.html +0 -5
  92. django_cfg/templates/admin/sections/widgets_section.html +0 -11
  93. django_cfg/templates/admin_old/components/action_grid.html +0 -49
  94. django_cfg/templates/admin_old/components/card.html +0 -50
  95. django_cfg/templates/admin_old/components/data_table.html +0 -67
  96. django_cfg/templates/admin_old/components/metric_card.html +0 -39
  97. django_cfg/templates/admin_old/components/modal.html +0 -58
  98. django_cfg/templates/admin_old/components/progress_bar.html +0 -20
  99. django_cfg/templates/admin_old/components/section_header.html +0 -26
  100. django_cfg/templates/admin_old/components/stat_item.html +0 -32
  101. django_cfg/templates/admin_old/components/stats_grid.html +0 -72
  102. django_cfg/templates/admin_old/components/status_badge.html +0 -28
  103. django_cfg/templates/admin_old/components/user_avatar.html +0 -27
  104. django_cfg/templates/admin_old/constance/change_list.html +0 -74
  105. django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
  106. django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
  107. django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
  108. django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
  109. django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
  110. django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
  111. django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
  112. django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
  113. django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
  114. django_cfg/templates/admin_old/index.html +0 -80
  115. django_cfg/templates/admin_old/index_new.html +0 -119
  116. django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
  117. django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
  118. django_cfg/templates/admin_old/sections/commands_section.html +0 -549
  119. django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
  120. django_cfg/templates/admin_old/sections/overview_section.html +0 -112
  121. django_cfg/templates/admin_old/sections/stats_section.html +0 -35
  122. django_cfg/templates/admin_old/sections/system_section.html +0 -99
  123. django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
  124. django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
  125. django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
  126. django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
  127. django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
  128. django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
  129. django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
  130. django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
  131. django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
  132. django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
  133. django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
  134. django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
  135. django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
  136. django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
  137. django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
  138. django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
  139. django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
  140. django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
  141. django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
  142. django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
  143. /django_cfg/static/frontend/admin/_next/static/{D_d9HRw5Yn7BRHAX5q89_ → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
  144. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
  145. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
  146. {django_cfg-1.4.88.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
@@ -1,313 +0,0 @@
1
- /**
2
- * Live Channels Module
3
- * Handles real-time channel monitoring from Centrifugo server
4
- */
5
- export class LiveChannelsModule {
6
- constructor(api, dashboard) {
7
- this.api = api;
8
- this.dashboard = dashboard;
9
- this.currentChannel = null;
10
- }
11
-
12
- async loadLiveChannels() {
13
- try {
14
- console.log('Loading live channels from Centrifugo server...');
15
-
16
- const patternInput = document.getElementById('live-channel-pattern');
17
- const pattern = patternInput ? patternInput.value.trim() : '';
18
-
19
- // Call Centrifugo channels API
20
- const data = { pattern: pattern || '' };
21
- const response = await this.api.centrifugoAdminApiServerChannelsCreate(data);
22
-
23
- if (response && response.result) {
24
- const channels = response.result.channels || {};
25
- this.renderChannelsGrid(channels);
26
- this.updateLiveChannelsBadge(Object.keys(channels).length);
27
- } else if (response && response.error) {
28
- console.error('Centrifugo API error:', response.error);
29
- this.showError('Failed to load channels: ' + response.error.message);
30
- }
31
- } catch (error) {
32
- console.error('Error loading live channels:', error);
33
- this.showError('Failed to connect to Centrifugo server');
34
- }
35
- }
36
-
37
- renderChannelsGrid(channels) {
38
- const grid = document.getElementById('live-channels-grid');
39
- const emptyState = document.getElementById('live-channels-empty');
40
-
41
- if (!grid) return;
42
-
43
- const channelEntries = Object.entries(channels);
44
-
45
- if (channelEntries.length === 0) {
46
- grid.innerHTML = '';
47
- if (emptyState) emptyState.classList.remove('hidden');
48
- return;
49
- }
50
-
51
- if (emptyState) emptyState.classList.add('hidden');
52
-
53
- let html = '';
54
- channelEntries.forEach(([channelName, info]) => {
55
- const numClients = info.num_clients || 0;
56
- const statusColor = numClients > 0 ? 'text-green-500' : 'text-gray-400';
57
- const statusIcon = numClients > 0 ? 'radio_button_checked' : 'radio_button_unchecked';
58
-
59
- html += `
60
- <div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 hover:border-purple-500 dark:hover:border-purple-400 transition-colors">
61
- <!-- Channel Header -->
62
- <div class="flex items-start justify-between mb-3">
63
- <div class="flex items-center gap-2 min-w-0 flex-1">
64
- <span class="material-icons ${statusColor}">${statusIcon}</span>
65
- <code class="text-sm font-mono text-gray-900 dark:text-white truncate">${this.escapeHtml(channelName)}</code>
66
- </div>
67
- </div>
68
-
69
- <!-- Stats -->
70
- <div class="grid grid-cols-2 gap-3 mb-3">
71
- <div>
72
- <p class="text-xs text-gray-600 dark:text-gray-400">Clients</p>
73
- <p class="text-xl font-bold text-purple-600 dark:text-purple-400">${numClients}</p>
74
- </div>
75
- <div>
76
- <p class="text-xs text-gray-600 dark:text-gray-400">Status</p>
77
- <p class="text-sm font-medium ${numClients > 0 ? 'text-green-600 dark:text-green-400' : 'text-gray-500 dark:text-gray-400'}">
78
- ${numClients > 0 ? 'Active' : 'Idle'}
79
- </p>
80
- </div>
81
- </div>
82
-
83
- <!-- Actions -->
84
- <div class="flex gap-2">
85
- <button onclick="window.centrifugoDashboard.liveChannelsModule.viewChannelDetails('${this.escapeHtml(channelName)}')"
86
- class="flex-1 px-3 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded text-sm font-medium flex items-center justify-center gap-1">
87
- <span class="material-icons text-base">visibility</span>
88
- View Details
89
- </button>
90
- </div>
91
- </div>
92
- `;
93
- });
94
-
95
- grid.innerHTML = html;
96
- }
97
-
98
- async viewChannelDetails(channel) {
99
- this.currentChannel = channel;
100
- const modal = document.getElementById('channel-details-modal');
101
- if (!modal) return;
102
-
103
- // Show modal
104
- modal.classList.remove('hidden');
105
-
106
- // Set channel name
107
- const modalTitle = document.getElementById('modal-channel-name');
108
- if (modalTitle) {
109
- modalTitle.textContent = channel;
110
- }
111
-
112
- // Load presence and history
113
- await this.loadChannelPresence(channel);
114
- await this.loadChannelHistory(channel);
115
- await this.loadPresenceStats(channel);
116
- }
117
-
118
- async loadPresenceStats(channel) {
119
- try {
120
- const data = { channel };
121
- const response = await this.api.centrifugoAdminApiServerPresenceStatsCreate(data);
122
-
123
- if (response && response.error) {
124
- // Feature not available (e.g., code 108)
125
- console.warn('Presence stats not available:', response.error.message);
126
- this.updateElement('modal-clients-count', 'N/A');
127
- this.updateElement('modal-users-count', 'N/A');
128
- } else if (response && response.result) {
129
- const stats = response.result;
130
- this.updateElement('modal-clients-count', stats.num_clients || 0);
131
- this.updateElement('modal-users-count', stats.num_users || 0);
132
- }
133
- } catch (error) {
134
- console.error('Error loading presence stats:', error);
135
- this.updateElement('modal-clients-count', 'Error');
136
- this.updateElement('modal-users-count', 'Error');
137
- }
138
- }
139
-
140
- async loadChannelPresence(channel) {
141
- try {
142
- const data = { channel };
143
- const response = await this.api.centrifugoAdminApiServerPresenceCreate(data);
144
-
145
- if (response && response.error) {
146
- // Feature not available (e.g., code 108)
147
- console.warn('Presence not available:', response.error.message);
148
- this.renderPresenceList({}, 'Presence tracking not enabled for this channel');
149
- } else if (response && response.result) {
150
- const presence = response.result.presence || {};
151
- this.renderPresenceList(presence);
152
- }
153
- } catch (error) {
154
- console.error('Error loading channel presence:', error);
155
- this.renderPresenceList({}, 'Error loading presence data');
156
- }
157
- }
158
-
159
- renderPresenceList(presence, errorMessage = null) {
160
- const tbody = document.getElementById('modal-presence-list');
161
- if (!tbody) return;
162
-
163
- if (errorMessage) {
164
- tbody.innerHTML = `
165
- <tr>
166
- <td colspan="3" class="px-4 py-8 text-center text-yellow-600 dark:text-yellow-400">
167
- <span class="material-icons text-2xl mb-2">info</span>
168
- <p>${errorMessage}</p>
169
- </td>
170
- </tr>
171
- `;
172
- return;
173
- }
174
-
175
- const clients = Object.values(presence);
176
-
177
- if (clients.length === 0) {
178
- tbody.innerHTML = `
179
- <tr>
180
- <td colspan="3" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">
181
- No clients connected to this channel
182
- </td>
183
- </tr>
184
- `;
185
- return;
186
- }
187
-
188
- let html = '';
189
- clients.forEach(client => {
190
- const userId = client.user || 'anonymous';
191
- const clientId = (client.client || '').substring(0, 12) + '...';
192
- const connInfo = client.conn_info ? JSON.stringify(client.conn_info) : '-';
193
-
194
- html += `
195
- <tr class="hover:bg-gray-50 dark:hover:bg-gray-700">
196
- <td class="px-4 py-3 text-sm font-medium text-gray-900 dark:text-white">${this.escapeHtml(userId)}</td>
197
- <td class="px-4 py-3 text-xs font-mono text-gray-600 dark:text-gray-400">${this.escapeHtml(clientId)}</td>
198
- <td class="px-4 py-3 text-xs text-gray-600 dark:text-gray-400">${this.escapeHtml(connInfo)}</td>
199
- </tr>
200
- `;
201
- });
202
-
203
- tbody.innerHTML = html;
204
- }
205
-
206
- async loadChannelHistory(channel) {
207
- try {
208
- const data = { channel, limit: 10 };
209
- const response = await this.api.centrifugoAdminApiServerHistoryCreate(data);
210
-
211
- if (response && response.error) {
212
- // Feature not available (e.g., code 108)
213
- console.warn('History not available:', response.error.message);
214
- this.renderHistoryList([], 'History not enabled for this channel');
215
- this.updateElement('modal-history-count', 'N/A');
216
- } else if (response && response.result) {
217
- const publications = response.result.publications || [];
218
- this.renderHistoryList(publications);
219
- this.updateElement('modal-history-count', publications.length);
220
- }
221
- } catch (error) {
222
- console.error('Error loading channel history:', error);
223
- this.renderHistoryList([], 'Error loading history');
224
- this.updateElement('modal-history-count', 'Error');
225
- }
226
- }
227
-
228
- renderHistoryList(publications, errorMessage = null) {
229
- const container = document.getElementById('modal-history-list');
230
- if (!container) return;
231
-
232
- if (errorMessage) {
233
- container.innerHTML = `
234
- <p class="text-sm text-yellow-600 dark:text-yellow-400 text-center py-4">
235
- <span class="material-icons text-xl">info</span><br>
236
- ${errorMessage}
237
- </p>
238
- `;
239
- return;
240
- }
241
-
242
- if (publications.length === 0) {
243
- container.innerHTML = `
244
- <p class="text-sm text-gray-500 dark:text-gray-400 text-center py-4">
245
- No recent messages in this channel
246
- </p>
247
- `;
248
- return;
249
- }
250
-
251
- let html = '';
252
- publications.forEach((pub, index) => {
253
- const data = pub.data || {};
254
- const offset = pub.offset || '-';
255
- const dataStr = JSON.stringify(data, null, 2);
256
-
257
- html += `
258
- <div class="bg-gray-50 dark:bg-gray-700 rounded-lg p-4">
259
- <div class="flex items-center justify-between mb-2">
260
- <span class="text-xs font-medium text-gray-600 dark:text-gray-400">Message #${index + 1}</span>
261
- <span class="text-xs text-gray-500 dark:text-gray-400">Offset: ${offset}</span>
262
- </div>
263
- <pre class="text-xs text-gray-900 dark:text-white overflow-x-auto"><code>${this.escapeHtml(dataStr)}</code></pre>
264
- </div>
265
- `;
266
- });
267
-
268
- container.innerHTML = html;
269
- }
270
-
271
- closeModal() {
272
- const modal = document.getElementById('channel-details-modal');
273
- if (modal) {
274
- modal.classList.add('hidden');
275
- }
276
- this.currentChannel = null;
277
- }
278
-
279
- updateLiveChannelsBadge(count) {
280
- const badge = document.getElementById('live-channels-count-badge');
281
- if (badge) {
282
- badge.textContent = count;
283
- }
284
- }
285
-
286
- showError(message) {
287
- const grid = document.getElementById('live-channels-grid');
288
- if (grid) {
289
- grid.innerHTML = `
290
- <div class="col-span-full bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4">
291
- <div class="flex items-center gap-3">
292
- <span class="material-icons text-red-500">error</span>
293
- <p class="text-sm text-red-700 dark:text-red-400">${this.escapeHtml(message)}</p>
294
- </div>
295
- </div>
296
- `;
297
- }
298
- }
299
-
300
- updateElement(id, value) {
301
- const element = document.getElementById(id);
302
- if (element) {
303
- element.textContent = value;
304
- }
305
- }
306
-
307
- escapeHtml(unsafe) {
308
- if (typeof unsafe !== 'string') return unsafe;
309
- const div = document.createElement('div');
310
- div.textContent = unsafe;
311
- return div.innerHTML;
312
- }
313
- }