django-cfg 1.4.87__py3-none-any.whl → 1.4.89__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of django-cfg might be problematic. Click here for more details.
- django_cfg/__init__.py +1 -1
- django_cfg/apps/centrifugo/views/__init__.py +0 -2
- django_cfg/apps/dashboard/permissions.py +48 -0
- django_cfg/apps/dashboard/serializers/__init__.py +8 -1
- django_cfg/apps/dashboard/serializers/commands.py +29 -0
- django_cfg/apps/dashboard/services/__init__.py +2 -0
- django_cfg/{modules/django_unfold/callbacks/base.py → apps/dashboard/services/commands_security.py} +28 -90
- django_cfg/apps/dashboard/services/commands_service.py +208 -9
- django_cfg/apps/dashboard/services/overview_service.py +205 -0
- django_cfg/apps/dashboard/views/commands_views.py +92 -4
- django_cfg/apps/frontend/test_routing.py +134 -0
- django_cfg/apps/frontend/views.py +73 -28
- django_cfg/apps/urls.py +0 -1
- django_cfg/core/builders/apps_builder.py +0 -58
- django_cfg/modules/django_unfold/__init__.py +5 -24
- django_cfg/modules/django_unfold/models/__init__.py +0 -23
- django_cfg/modules/django_unfold/models/config.py +11 -65
- django_cfg/modules/django_unfold/{dashboard.py → navigation.py} +21 -152
- django_cfg/modules/django_unfold/tailwind.py +2 -4
- django_cfg/pyproject.toml +1 -1
- django_cfg/registry/third_party.py +0 -9
- django_cfg/routing/callbacks.py +1 -43
- django_cfg/static/frontend/admin/404/index.html +1 -1
- django_cfg/static/frontend/admin/404.html +1 -1
- django_cfg/static/frontend/admin/500/index.html +1 -1
- django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_buildManifest.js +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/{19430.fe7bff7372f8a256.js → 19430.c4c95603c23c17fe.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-9443faa6df24aebf.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/94141-bc6d47f419b26b21.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/pages/{_app-c336f254967dd101.js → _app-c7dcd3aa616fab68.js} +6 -6
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{cookies-b39c7f22c066e2c6.js → cookies-97d279800f12aab4.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{privacy-5aedad0cf3a4f80f.js → privacy-1d5e6cd94689247e.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{security-dbd854d0d5d483e2.js → security-55e49700e7a01f5a.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/legal/{terms-f3e1d2b9e5edf12f.js → terms-14c02bb2d3198352.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private/{centrifugo-22532c65971225eb.js → centrifugo-f9ecbc3ae0052a03.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-d4ccbe1265cbd853.js +1 -0
- django_cfg/static/frontend/admin/_next/static/chunks/{webpack-da114020a6b940f5.js → webpack-5a92f81363b62aa7.js} +1 -1
- django_cfg/static/frontend/admin/_next/static/css/3063068f0d5a8a00.css +3 -0
- django_cfg/static/frontend/admin/_next/static/media/19cfc7226ec3afaa-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/21350d82a1f187e9-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/8e9860b6e62d6359-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/ba9851c3c22cd980-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/c5fe6dc8356a8c31-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/df0a9ae256c0569c-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/e4af272ccee01ff0-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/auth/index.html +1 -1
- django_cfg/static/frontend/admin/index.html +1 -1
- django_cfg/static/frontend/admin/legal/cookies/index.html +1 -1
- django_cfg/static/frontend/admin/legal/privacy/index.html +1 -1
- django_cfg/static/frontend/admin/legal/security/index.html +1 -1
- django_cfg/static/frontend/admin/legal/terms/index.html +1 -1
- django_cfg/static/frontend/admin/private/centrifugo/index.html +1 -1
- django_cfg/static/frontend/admin/private/index.html +1 -1
- django_cfg/static/frontend/admin/private/profile/index.html +1 -1
- django_cfg/static/frontend/admin/private/ui/index.html +2 -2
- django_cfg/templates/admin/index.html +1 -1
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/METADATA +1 -1
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/RECORD +62 -163
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/css/dashboard.css +0 -260
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_channels.mjs +0 -313
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/live_testing.mjs +0 -803
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/main.mjs +0 -341
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/overview.mjs +0 -432
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/testing.mjs +0 -33
- django_cfg/apps/centrifugo/static/django_cfg_centrifugo/js/dashboard/websocket.mjs +0 -210
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/channels_content.html +0 -46
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/live_channels_content.html +0 -123
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/overview_content.html +0 -45
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/publishes_content.html +0 -84
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/stat_cards.html +0 -53
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/system_status.html +0 -91
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/tab_navigation.html +0 -29
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/components/testing_tools.html +0 -415
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/layout/base.html +0 -61
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/pages/dashboard.html +0 -58
- django_cfg/apps/centrifugo/templates/django_cfg_centrifugo/tags/connection_script.html +0 -48
- django_cfg/apps/centrifugo/templatetags/__init__.py +0 -1
- django_cfg/apps/centrifugo/templatetags/centrifugo_tags.py +0 -81
- django_cfg/apps/centrifugo/urls_admin.py +0 -20
- django_cfg/apps/centrifugo/views/dashboard.py +0 -28
- django_cfg/modules/django_dashboard/__init__.py +0 -23
- django_cfg/modules/django_dashboard/components.py +0 -312
- django_cfg/modules/django_dashboard/debug.py +0 -174
- django_cfg/modules/django_dashboard/management/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/__init__.py +0 -0
- django_cfg/modules/django_dashboard/management/commands/debug_dashboard.py +0 -109
- django_cfg/modules/django_dashboard/sections/__init__.py +0 -1
- django_cfg/modules/django_dashboard/sections/base.py +0 -129
- django_cfg/modules/django_dashboard/sections/commands.py +0 -33
- django_cfg/modules/django_dashboard/sections/documentation.py +0 -393
- django_cfg/modules/django_dashboard/sections/overview.py +0 -398
- django_cfg/modules/django_dashboard/sections/stats.py +0 -48
- django_cfg/modules/django_dashboard/sections/system.py +0 -74
- django_cfg/modules/django_dashboard/sections/widgets.py +0 -222
- django_cfg/modules/django_unfold/callbacks/__init__.py +0 -9
- django_cfg/modules/django_unfold/callbacks/actions.py +0 -51
- django_cfg/modules/django_unfold/callbacks/apizones.py +0 -122
- django_cfg/modules/django_unfold/callbacks/charts.py +0 -223
- django_cfg/modules/django_unfold/callbacks/commands.py +0 -40
- django_cfg/modules/django_unfold/callbacks/main.py +0 -322
- django_cfg/modules/django_unfold/callbacks/statistics.py +0 -240
- django_cfg/modules/django_unfold/callbacks/system.py +0 -180
- django_cfg/modules/django_unfold/callbacks/users.py +0 -65
- django_cfg/modules/django_unfold/models/dashboard.py +0 -207
- django_cfg/modules/django_unfold/models/tabs.py +0 -26
- django_cfg/modules/django_unfold/models.py +0 -98
- django_cfg/modules/django_unfold/templates/unfold/helpers/app_list.html +0 -102
- django_cfg/static/frontend/admin/_next/static/chunks/23004-faae121bbfecc163.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/50314-48bd5701f62faf27.js +0 -1
- django_cfg/static/frontend/admin/_next/static/chunks/pages/private-fe9faa86ecdb0ce6.js +0 -1
- django_cfg/static/frontend/admin/_next/static/css/5f9a37b6e6a72303.css +0 -3
- django_cfg/static/frontend/admin/_next/static/media/438aa629764e75f3-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/4c9affa5bc8f420e-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/51251f8b9793cdb3-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/875ae681bfde4580-s.p.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/cc978ac5ee68c2b6-s.woff2 +0 -0
- django_cfg/static/frontend/admin/_next/static/media/e857b654a2caa584-s.woff2 +0 -0
- django_cfg/templates/admin/sections/commands_section.html +0 -5
- django_cfg/templates/admin/sections/documentation_section.html +0 -5
- django_cfg/templates/admin/sections/overview_section.html +0 -5
- django_cfg/templates/admin/sections/stats_section.html +0 -5
- django_cfg/templates/admin/sections/system_section.html +0 -5
- django_cfg/templates/admin/sections/widgets_section.html +0 -11
- django_cfg/templates/admin_old/components/action_grid.html +0 -49
- django_cfg/templates/admin_old/components/card.html +0 -50
- django_cfg/templates/admin_old/components/data_table.html +0 -67
- django_cfg/templates/admin_old/components/metric_card.html +0 -39
- django_cfg/templates/admin_old/components/modal.html +0 -58
- django_cfg/templates/admin_old/components/progress_bar.html +0 -20
- django_cfg/templates/admin_old/components/section_header.html +0 -26
- django_cfg/templates/admin_old/components/stat_item.html +0 -32
- django_cfg/templates/admin_old/components/stats_grid.html +0 -72
- django_cfg/templates/admin_old/components/status_badge.html +0 -28
- django_cfg/templates/admin_old/components/user_avatar.html +0 -27
- django_cfg/templates/admin_old/constance/change_list.html +0 -74
- django_cfg/templates/admin_old/constance/includes/default_value.html +0 -24
- django_cfg/templates/admin_old/constance/includes/fieldset_header.html +0 -15
- django_cfg/templates/admin_old/constance/includes/results_list.html +0 -16
- django_cfg/templates/admin_old/constance/includes/setting_row.html +0 -50
- django_cfg/templates/admin_old/constance/includes/table_headers.html +0 -10
- django_cfg/templates/admin_old/examples/component_class_example.html +0 -156
- django_cfg/templates/admin_old/import_export/change_list_export.html +0 -24
- django_cfg/templates/admin_old/import_export/change_list_import.html +0 -24
- django_cfg/templates/admin_old/import_export/change_list_import_export.html +0 -34
- django_cfg/templates/admin_old/index.html +0 -80
- django_cfg/templates/admin_old/index_new.html +0 -119
- django_cfg/templates/admin_old/layouts/base_dashboard.html +0 -62
- django_cfg/templates/admin_old/layouts/dashboard_with_tabs.html +0 -176
- django_cfg/templates/admin_old/sections/commands_section.html +0 -549
- django_cfg/templates/admin_old/sections/documentation_section.html +0 -152
- django_cfg/templates/admin_old/sections/overview_section.html +0 -112
- django_cfg/templates/admin_old/sections/stats_section.html +0 -35
- django_cfg/templates/admin_old/sections/system_section.html +0 -99
- django_cfg/templates/admin_old/sections/widgets_section.html +0 -129
- django_cfg/templates/admin_old/snippets/components/activity_tracker.html +0 -70
- django_cfg/templates/admin_old/snippets/components/charts_section.html +0 -113
- django_cfg/templates/admin_old/snippets/components/django_commands.html +0 -270
- django_cfg/templates/admin_old/snippets/components/quick_actions.html +0 -66
- django_cfg/templates/admin_old/snippets/components/recent_activity_improved.html +0 -25
- django_cfg/templates/admin_old/snippets/components/recent_users_table.html +0 -102
- django_cfg/templates/admin_old/snippets/components/stats_cards.html +0 -4
- django_cfg/templates/admin_old/snippets/components/stats_tiles.html +0 -92
- django_cfg/templates/admin_old/snippets/components/system_health.html +0 -22
- django_cfg/templates/admin_old/snippets/components/system_metrics.html +0 -199
- django_cfg/templates/admin_old/snippets/components/user_permissions.html +0 -57
- django_cfg/templates/admin_old/snippets/tabs/app_stats_tab.html +0 -201
- django_cfg/templates/admin_old/snippets/tabs/commands_tab.html +0 -114
- django_cfg/templates/admin_old/snippets/tabs/documentation_tab.html +0 -42
- django_cfg/templates/admin_old/snippets/tabs/overview_tab.html +0 -116
- django_cfg/templates/admin_old/snippets/tabs/stats_tab.html +0 -89
- django_cfg/templates/admin_old/snippets/tabs/users_tab.html +0 -51
- django_cfg/templates/admin_old/snippets/tabs/widgets_tab.html +0 -38
- django_cfg/templates/admin_old/snippets/zones/zones_table.html +0 -176
- /django_cfg/static/frontend/admin/_next/static/{ZJZBgOL9mO1koHrgaaLEV → 0sN9ktsgXH48ygtGSrhfu}/_ssgManifest.js +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/WHEEL +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/entry_points.txt +0 -0
- {django_cfg-1.4.87.dist-info → django_cfg-1.4.89.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,432 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centrifugo Overview Dashboard Module
|
|
3
|
-
* Handles overview, publishes, and channels tabs
|
|
4
|
-
*/
|
|
5
|
-
export class OverviewModule {
|
|
6
|
-
constructor(api, dashboard) {
|
|
7
|
-
this.api = api;
|
|
8
|
-
this.dashboard = dashboard;
|
|
9
|
-
this.charts = {};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
async loadData(tabName) {
|
|
13
|
-
switch (tabName) {
|
|
14
|
-
case 'overview':
|
|
15
|
-
await this.loadOverviewCharts();
|
|
16
|
-
break;
|
|
17
|
-
case 'publishes':
|
|
18
|
-
await this.loadRecentPublishes();
|
|
19
|
-
break;
|
|
20
|
-
case 'channels':
|
|
21
|
-
await this.loadChannelStats();
|
|
22
|
-
break;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async loadOverviewStats() {
|
|
27
|
-
try {
|
|
28
|
-
const stats = await this.api.centrifugoAdminApiMonitorOverviewRetrieve({ hours: 24 });
|
|
29
|
-
|
|
30
|
-
if (stats) {
|
|
31
|
-
this.updateElement('total-publishes', stats.total || 0);
|
|
32
|
-
this.updateElement('avg-duration', (stats.avg_duration_ms || 0).toFixed(0));
|
|
33
|
-
this.updateElement('failed-count', stats.failed || 0);
|
|
34
|
-
|
|
35
|
-
const successRate = stats.success_rate || 0;
|
|
36
|
-
const successRateElement = document.getElementById('success-rate-value');
|
|
37
|
-
if (successRateElement) {
|
|
38
|
-
const rateSpan = successRateElement.querySelector('span');
|
|
39
|
-
if (rateSpan) {
|
|
40
|
-
rateSpan.textContent = successRate.toFixed(1);
|
|
41
|
-
rateSpan.className = this.getSuccessRateClass(successRate);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Note: trend is not provided by API, remove if UI expects it
|
|
46
|
-
const trendElement = document.getElementById('publish-trend');
|
|
47
|
-
if (trendElement) {
|
|
48
|
-
trendElement.style.display = 'none'; // hide trend since API doesn't provide it
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error('Error loading overview stats:', error);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async loadOverviewCharts() {
|
|
57
|
-
try {
|
|
58
|
-
console.log('📊 Loading overview charts...');
|
|
59
|
-
const stats = await this.api.centrifugoAdminApiMonitorOverviewRetrieve({ hours: 24 });
|
|
60
|
-
|
|
61
|
-
console.log('📊 Overview stats:', stats);
|
|
62
|
-
if (stats) {
|
|
63
|
-
// Render both charts with current stats
|
|
64
|
-
this.renderTimelinePlaceholder(stats);
|
|
65
|
-
this.renderBreakdownChart(stats);
|
|
66
|
-
this.updateAckStats(stats);
|
|
67
|
-
}
|
|
68
|
-
} catch (error) {
|
|
69
|
-
console.error('Error loading overview charts:', error);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
renderTimelinePlaceholder(stats) {
|
|
74
|
-
const canvas = document.getElementById('publish-timeline-chart');
|
|
75
|
-
if (!canvas) return;
|
|
76
|
-
|
|
77
|
-
const hasData = stats.total > 0;
|
|
78
|
-
|
|
79
|
-
// If no data, show text message instead of empty chart
|
|
80
|
-
if (!hasData) {
|
|
81
|
-
const parent = canvas.parentElement;
|
|
82
|
-
parent.innerHTML = `
|
|
83
|
-
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4 flex items-center">
|
|
84
|
-
<span class="material-icons mr-2 text-purple-500">timeline</span>
|
|
85
|
-
Publish Timeline (24h)
|
|
86
|
-
</h3>
|
|
87
|
-
<div class="flex items-center justify-center h-[200px] bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
88
|
-
<div class="text-center">
|
|
89
|
-
<span class="material-icons text-6xl text-gray-300 dark:text-gray-600 mb-2">show_chart</span>
|
|
90
|
-
<p class="text-gray-500 dark:text-gray-400">No publish data yet</p>
|
|
91
|
-
<p class="text-sm text-gray-400 dark:text-gray-500">Try running a Quick Test Scenario</p>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
`;
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const ctx = canvas.getContext('2d');
|
|
99
|
-
|
|
100
|
-
if (this.charts.timeline) {
|
|
101
|
-
this.charts.timeline.destroy();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
console.log('📊 Rendering timeline chart:', { total: stats.total, hasData });
|
|
105
|
-
|
|
106
|
-
this.charts.timeline = new Chart(ctx, {
|
|
107
|
-
type: 'line',
|
|
108
|
-
data: {
|
|
109
|
-
labels: ['Now'],
|
|
110
|
-
datasets: [{
|
|
111
|
-
label: 'Publishes',
|
|
112
|
-
data: [stats.total],
|
|
113
|
-
borderColor: 'rgb(168, 85, 247)',
|
|
114
|
-
backgroundColor: 'rgba(168, 85, 247, 0.1)',
|
|
115
|
-
tension: 0.4,
|
|
116
|
-
fill: true
|
|
117
|
-
}]
|
|
118
|
-
},
|
|
119
|
-
options: {
|
|
120
|
-
responsive: true,
|
|
121
|
-
maintainAspectRatio: false,
|
|
122
|
-
plugins: {
|
|
123
|
-
legend: {
|
|
124
|
-
display: false
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
scales: {
|
|
128
|
-
y: {
|
|
129
|
-
beginAtZero: true
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
renderTimelineChart(timeline) {
|
|
137
|
-
const canvas = document.getElementById('publish-timeline-chart');
|
|
138
|
-
if (!canvas) return;
|
|
139
|
-
|
|
140
|
-
const ctx = canvas.getContext('2d');
|
|
141
|
-
|
|
142
|
-
if (this.charts.timeline) {
|
|
143
|
-
this.charts.timeline.destroy();
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const labels = timeline.map(t => new Date(t.timestamp).toLocaleTimeString());
|
|
147
|
-
const data = timeline.map(t => t.count);
|
|
148
|
-
|
|
149
|
-
this.charts.timeline = new Chart(ctx, {
|
|
150
|
-
type: 'line',
|
|
151
|
-
data: {
|
|
152
|
-
labels: labels,
|
|
153
|
-
datasets: [{
|
|
154
|
-
label: 'Publishes',
|
|
155
|
-
data: data,
|
|
156
|
-
borderColor: 'rgb(168, 85, 247)',
|
|
157
|
-
backgroundColor: 'rgba(168, 85, 247, 0.1)',
|
|
158
|
-
tension: 0.4,
|
|
159
|
-
fill: true
|
|
160
|
-
}]
|
|
161
|
-
},
|
|
162
|
-
options: {
|
|
163
|
-
responsive: true,
|
|
164
|
-
maintainAspectRatio: false,
|
|
165
|
-
plugins: {
|
|
166
|
-
legend: {
|
|
167
|
-
display: false
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
scales: {
|
|
171
|
-
y: {
|
|
172
|
-
beginAtZero: true
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
renderBreakdownChart(stats) {
|
|
180
|
-
const canvas = document.getElementById('status-breakdown-chart');
|
|
181
|
-
if (!canvas) return;
|
|
182
|
-
|
|
183
|
-
const success = stats.successful || 0;
|
|
184
|
-
const failed = stats.failed || 0;
|
|
185
|
-
const timeout = stats.timeout || 0;
|
|
186
|
-
const hasData = success + failed + timeout > 0;
|
|
187
|
-
|
|
188
|
-
// If no data, show text message instead of empty chart
|
|
189
|
-
if (!hasData) {
|
|
190
|
-
const parent = canvas.parentElement;
|
|
191
|
-
parent.innerHTML = `
|
|
192
|
-
<h3 class="text-lg font-bold text-gray-900 dark:text-white mb-4 flex items-center">
|
|
193
|
-
<span class="material-icons mr-2 text-purple-500">pie_chart</span>
|
|
194
|
-
Success/Failure Breakdown
|
|
195
|
-
</h3>
|
|
196
|
-
<div class="flex items-center justify-center h-[200px] bg-gray-50 dark:bg-gray-700 rounded-lg">
|
|
197
|
-
<div class="text-center">
|
|
198
|
-
<span class="material-icons text-6xl text-gray-300 dark:text-gray-600 mb-2">pie_chart</span>
|
|
199
|
-
<p class="text-gray-500 dark:text-gray-400">No status data yet</p>
|
|
200
|
-
<p class="text-sm text-gray-400 dark:text-gray-500">Publish messages to see breakdown</p>
|
|
201
|
-
</div>
|
|
202
|
-
</div>
|
|
203
|
-
`;
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const ctx = canvas.getContext('2d');
|
|
208
|
-
|
|
209
|
-
if (this.charts.breakdown) {
|
|
210
|
-
this.charts.breakdown.destroy();
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
console.log('📊 Rendering breakdown chart:', { success, failed, timeout, hasData });
|
|
214
|
-
|
|
215
|
-
this.charts.breakdown = new Chart(ctx, {
|
|
216
|
-
type: 'doughnut',
|
|
217
|
-
data: {
|
|
218
|
-
labels: ['Success', 'Failed', 'Timeout'],
|
|
219
|
-
datasets: [{
|
|
220
|
-
data: [success, failed, timeout],
|
|
221
|
-
backgroundColor: [
|
|
222
|
-
'rgb(34, 197, 94)',
|
|
223
|
-
'rgb(239, 68, 68)',
|
|
224
|
-
'rgb(245, 158, 11)'
|
|
225
|
-
]
|
|
226
|
-
}]
|
|
227
|
-
},
|
|
228
|
-
options: {
|
|
229
|
-
responsive: true,
|
|
230
|
-
maintainAspectRatio: false,
|
|
231
|
-
plugins: {
|
|
232
|
-
legend: {
|
|
233
|
-
position: 'bottom',
|
|
234
|
-
labels: {
|
|
235
|
-
generateLabels: function(chart) {
|
|
236
|
-
const data = chart.data;
|
|
237
|
-
if (data.labels.length && data.datasets.length) {
|
|
238
|
-
return data.labels.map((label, i) => {
|
|
239
|
-
const value = data.datasets[0].data[i];
|
|
240
|
-
return {
|
|
241
|
-
text: `${label}: ${value}`,
|
|
242
|
-
fillStyle: data.datasets[0].backgroundColor[i],
|
|
243
|
-
hidden: false,
|
|
244
|
-
index: i
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
return [];
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
updateAckStats(stats) {
|
|
258
|
-
// API returns avg_acks_received directly, not nested in ack_stats
|
|
259
|
-
this.updateElement('avg-acks', (stats.avg_acks_received || 0).toFixed(1));
|
|
260
|
-
|
|
261
|
-
// Calculate total ACKs if we need to display it (not in API response)
|
|
262
|
-
const totalAcks = Math.round((stats.total || 0) * (stats.avg_acks_received || 0));
|
|
263
|
-
this.updateElement('total-acks', totalAcks);
|
|
264
|
-
|
|
265
|
-
// ACK tracking rate not provided by API - could be calculated from publishes data
|
|
266
|
-
// For now, hide or use a placeholder
|
|
267
|
-
const ackRateElement = document.getElementById('ack-rate');
|
|
268
|
-
if (ackRateElement && ackRateElement.parentElement) {
|
|
269
|
-
ackRateElement.parentElement.style.display = 'none';
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
async loadRecentPublishes() {
|
|
274
|
-
try {
|
|
275
|
-
const channelFilter = document.getElementById('publish-channel-filter')?.value || '';
|
|
276
|
-
// Note: status filtering is done client-side since API doesn't support it
|
|
277
|
-
const statusFilter = document.getElementById('publish-status-filter')?.value || '';
|
|
278
|
-
|
|
279
|
-
const params = { count: 50 };
|
|
280
|
-
if (channelFilter) {
|
|
281
|
-
params.channel = channelFilter;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const data = await this.api.centrifugoAdminApiMonitorPublishesRetrieve(params);
|
|
285
|
-
|
|
286
|
-
if (data) {
|
|
287
|
-
let publishes = data.publishes || [];
|
|
288
|
-
|
|
289
|
-
// Apply client-side status filter if selected
|
|
290
|
-
if (statusFilter) {
|
|
291
|
-
publishes = publishes.filter(pub => pub.status === statusFilter);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
this.renderPublishesTable(publishes);
|
|
295
|
-
this.updateElement('publishes-showing', publishes.length);
|
|
296
|
-
|
|
297
|
-
const channels = data.available_channels || [];
|
|
298
|
-
this.updateChannelFilter(channels);
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
console.error('Error loading recent publishes:', error);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
renderPublishesTable(publishes) {
|
|
306
|
-
const tbody = document.getElementById('publishes-table-body');
|
|
307
|
-
if (!tbody) return;
|
|
308
|
-
|
|
309
|
-
if (publishes.length === 0) {
|
|
310
|
-
tbody.innerHTML = '<tr><td colspan="6" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">No publishes found</td></tr>';
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
let html = '';
|
|
315
|
-
publishes.forEach(pub => {
|
|
316
|
-
const statusClass = this.getStatusBadgeClass(pub.status);
|
|
317
|
-
// API returns: acks_received, acks_expected (not expected_acks)
|
|
318
|
-
const ackText = pub.acks_expected > 0 ? pub.acks_received + '/' + pub.acks_expected : 'N/A';
|
|
319
|
-
|
|
320
|
-
html += '<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">';
|
|
321
|
-
// API returns: created_at (not timestamp)
|
|
322
|
-
html += '<td class="px-4 py-3 text-sm">' + this.formatTimestamp(pub.created_at) + '</td>';
|
|
323
|
-
html += '<td class="px-4 py-3 text-xs font-mono">' + this.escapeHtml((pub.message_id || '').substring(0, 12)) + '...</td>';
|
|
324
|
-
html += '<td class="px-4 py-3"><code class="text-sm bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">' + this.escapeHtml(pub.channel) + '</code></td>';
|
|
325
|
-
html += '<td class="px-4 py-3"><span class="status-badge ' + statusClass + '">' + this.escapeHtml(pub.status) + '</span></td>';
|
|
326
|
-
html += '<td class="px-4 py-3 text-sm">' + ackText + '</td>';
|
|
327
|
-
html += '<td class="px-4 py-3 text-sm">' + (pub.duration_ms || 0).toFixed(0) + 'ms</td>';
|
|
328
|
-
html += '</tr>';
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
tbody.innerHTML = html;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
updateChannelFilter(channels) {
|
|
335
|
-
const select = document.getElementById('publish-channel-filter');
|
|
336
|
-
if (!select) return;
|
|
337
|
-
|
|
338
|
-
const currentValue = select.value;
|
|
339
|
-
let html = '<option value="">All Channels</option>';
|
|
340
|
-
channels.forEach(channel => {
|
|
341
|
-
html += '<option value="' + this.escapeHtml(channel) + '">' + this.escapeHtml(channel) + '</option>';
|
|
342
|
-
});
|
|
343
|
-
select.innerHTML = html;
|
|
344
|
-
select.value = currentValue;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
async loadChannelStats() {
|
|
348
|
-
try {
|
|
349
|
-
const data = await this.api.centrifugoAdminApiMonitorChannelsRetrieve({ hours: 24 });
|
|
350
|
-
|
|
351
|
-
if (data) {
|
|
352
|
-
const channels = data.channels || [];
|
|
353
|
-
this.renderChannelsTable(channels);
|
|
354
|
-
}
|
|
355
|
-
} catch (error) {
|
|
356
|
-
console.error('Error loading channel stats:', error);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
renderChannelsTable(channels) {
|
|
361
|
-
const tbody = document.getElementById('channels-table-body');
|
|
362
|
-
if (!tbody) return;
|
|
363
|
-
|
|
364
|
-
if (channels.length === 0) {
|
|
365
|
-
tbody.innerHTML = '<tr><td colspan="6" class="px-4 py-8 text-center text-gray-500 dark:text-gray-400">No channel data available</td></tr>';
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
let html = '';
|
|
370
|
-
channels.forEach(channel => {
|
|
371
|
-
// API returns: total, successful, failed (not success_rate)
|
|
372
|
-
// Calculate success rate client-side
|
|
373
|
-
const total = channel.total || 0;
|
|
374
|
-
const successful = channel.successful || 0;
|
|
375
|
-
const successRate = total > 0 ? (successful / total) * 100 : 0;
|
|
376
|
-
const successClass = this.getSuccessRateClass(successRate);
|
|
377
|
-
|
|
378
|
-
html += '<tr class="hover:bg-gray-50 dark:hover:bg-gray-700">';
|
|
379
|
-
html += '<td class="px-4 py-3"><code class="text-sm bg-gray-100 dark:bg-gray-700 px-2 py-1 rounded">' + this.escapeHtml(channel.channel) + '</code></td>';
|
|
380
|
-
html += '<td class="px-4 py-3 text-sm font-medium">' + total + '</td>';
|
|
381
|
-
html += '<td class="px-4 py-3 text-sm"><span class="' + successClass + '">' + successRate.toFixed(1) + '%</span></td>';
|
|
382
|
-
html += '<td class="px-4 py-3 text-sm">' + (channel.avg_duration_ms || 0).toFixed(0) + 'ms</td>';
|
|
383
|
-
html += '<td class="px-4 py-3 text-sm">' + (channel.avg_acks || 0).toFixed(1) + '</td>';
|
|
384
|
-
// API doesn't provide last_activity, remove this column or use a placeholder
|
|
385
|
-
html += '<td class="px-4 py-3 text-sm text-gray-400">N/A</td>';
|
|
386
|
-
html += '</tr>';
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
tbody.innerHTML = html;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
getStatusBadgeClass(status) {
|
|
393
|
-
switch (status) {
|
|
394
|
-
case 'success':
|
|
395
|
-
return 'success';
|
|
396
|
-
case 'failed':
|
|
397
|
-
return 'failed';
|
|
398
|
-
case 'timeout':
|
|
399
|
-
return 'timeout';
|
|
400
|
-
default:
|
|
401
|
-
return '';
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
getSuccessRateClass(rate) {
|
|
406
|
-
if (rate >= 95) return 'success-rate-high';
|
|
407
|
-
if (rate >= 80) return 'success-rate-medium';
|
|
408
|
-
return 'success-rate-low';
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
updateElement(id, value) {
|
|
412
|
-
const element = document.getElementById(id);
|
|
413
|
-
if (element) {
|
|
414
|
-
element.textContent = value;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
formatTimestamp(isoString) {
|
|
419
|
-
try {
|
|
420
|
-
const date = new Date(isoString);
|
|
421
|
-
return date.toLocaleTimeString();
|
|
422
|
-
} catch {
|
|
423
|
-
return 'N/A';
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
escapeHtml(unsafe) {
|
|
428
|
-
const div = document.createElement('div');
|
|
429
|
-
div.textContent = unsafe;
|
|
430
|
-
return div.innerHTML;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centrifugo Usage Guide Module
|
|
3
|
-
* Handles usage guide tab functionality (read-only, no publish capabilities)
|
|
4
|
-
*/
|
|
5
|
-
export class UsageGuideModule {
|
|
6
|
-
constructor(api, dashboard) {
|
|
7
|
-
this.api = api;
|
|
8
|
-
this.dashboard = dashboard;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
init() {
|
|
12
|
-
this.setupEventListeners();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
setupEventListeners() {
|
|
16
|
-
// Apply filters button for publishes table (if exists in other tabs)
|
|
17
|
-
const applyFiltersBtn = document.getElementById('apply-publish-filters');
|
|
18
|
-
if (applyFiltersBtn) {
|
|
19
|
-
applyFiltersBtn.addEventListener('click', () => {
|
|
20
|
-
this.dashboard.overviewModule.loadRecentPublishes();
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// No publish functionality - this is a read-only guide
|
|
25
|
-
console.log('Usage Guide Module initialized (read-only)');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
escapeHtml(unsafe) {
|
|
29
|
-
const div = document.createElement('div');
|
|
30
|
-
div.textContent = unsafe;
|
|
31
|
-
return div.innerHTML;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WebSocket Real-time Updates Module
|
|
3
|
-
* Subscribes to Centrifugo channels for live dashboard updates
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export class WebSocketModule {
|
|
7
|
-
constructor(dashboard) {
|
|
8
|
-
this.dashboard = dashboard;
|
|
9
|
-
this.centrifuge = null;
|
|
10
|
-
this.subscription = null;
|
|
11
|
-
this.dashboardChannel = 'centrifugo#dashboard';
|
|
12
|
-
this.reconnectAttempts = 0;
|
|
13
|
-
this.maxReconnectAttempts = 5;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async init() {
|
|
17
|
-
console.log('🔌 Initializing WebSocket connection...');
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
// Get Centrifugo connection token
|
|
21
|
-
const tokenResponse = await this.dashboard.api.centrifugoAdminApiServerAuthTokenCreate();
|
|
22
|
-
|
|
23
|
-
if (!tokenResponse || !tokenResponse.token) {
|
|
24
|
-
console.error('❌ Failed to get connection token');
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const config = tokenResponse.config || {};
|
|
29
|
-
const wsUrl = config.centrifugo_url || 'ws://localhost:8002/connection/websocket';
|
|
30
|
-
|
|
31
|
-
console.log('🔌 Connecting to:', wsUrl);
|
|
32
|
-
|
|
33
|
-
// Initialize Centrifuge client
|
|
34
|
-
this.centrifuge = new Centrifuge(wsUrl, {
|
|
35
|
-
token: tokenResponse.token,
|
|
36
|
-
debug: true,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// Setup event handlers
|
|
40
|
-
this.setupEventHandlers();
|
|
41
|
-
|
|
42
|
-
// Connect
|
|
43
|
-
this.centrifuge.connect();
|
|
44
|
-
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.error('❌ WebSocket initialization error:', error);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
setupEventHandlers() {
|
|
51
|
-
// Connection events
|
|
52
|
-
this.centrifuge.on('connected', (ctx) => {
|
|
53
|
-
console.log('✅ WebSocket connected:', ctx);
|
|
54
|
-
this.reconnectAttempts = 0;
|
|
55
|
-
this.subscribeToDashboard();
|
|
56
|
-
this.updateConnectionStatus(true);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
this.centrifuge.on('disconnected', (ctx) => {
|
|
60
|
-
console.log('⚠️ WebSocket disconnected:', ctx);
|
|
61
|
-
this.updateConnectionStatus(false);
|
|
62
|
-
|
|
63
|
-
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
64
|
-
this.reconnectAttempts++;
|
|
65
|
-
console.log(`🔄 Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}`);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
this.centrifuge.on('error', (ctx) => {
|
|
70
|
-
console.error('❌ WebSocket error:', ctx);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
subscribeToDashboard() {
|
|
75
|
-
console.log('📡 Subscribing to dashboard channel:', this.dashboardChannel);
|
|
76
|
-
|
|
77
|
-
this.subscription = this.centrifuge.newSubscription(this.dashboardChannel);
|
|
78
|
-
|
|
79
|
-
this.subscription.on('publication', (ctx) => {
|
|
80
|
-
console.log('📨 Dashboard update received:', ctx.data);
|
|
81
|
-
this.handleDashboardUpdate(ctx.data);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
this.subscription.on('subscribed', (ctx) => {
|
|
85
|
-
console.log('✅ Subscribed to dashboard channel');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
this.subscription.on('error', (ctx) => {
|
|
89
|
-
console.error('❌ Subscription error:', ctx);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
this.subscription.subscribe();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
handleDashboardUpdate(data) {
|
|
96
|
-
const { type } = data;
|
|
97
|
-
|
|
98
|
-
switch (type) {
|
|
99
|
-
case 'new_publish':
|
|
100
|
-
this.handleNewPublish(data.publish);
|
|
101
|
-
break;
|
|
102
|
-
|
|
103
|
-
case 'status_change':
|
|
104
|
-
this.handleStatusChange(data.publish);
|
|
105
|
-
break;
|
|
106
|
-
|
|
107
|
-
case 'stats_update':
|
|
108
|
-
this.handleStatsUpdate();
|
|
109
|
-
break;
|
|
110
|
-
|
|
111
|
-
default:
|
|
112
|
-
console.log('⚠️ Unknown message type:', type);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
handleNewPublish(publish) {
|
|
117
|
-
console.log('🆕 New publish:', publish);
|
|
118
|
-
|
|
119
|
-
// Show notification
|
|
120
|
-
this.showNotification('New Publish', `Channel: ${publish.channel}`);
|
|
121
|
-
|
|
122
|
-
// Refresh overview stats
|
|
123
|
-
if (this.dashboard.overviewModule) {
|
|
124
|
-
this.dashboard.overviewModule.loadOverviewStats();
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// If on overview tab, refresh data
|
|
128
|
-
if (this.dashboard.currentTab === 'overview') {
|
|
129
|
-
this.dashboard.overviewModule.loadData('overview');
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
handleStatusChange(publish) {
|
|
134
|
-
console.log('🔄 Status change:', publish);
|
|
135
|
-
|
|
136
|
-
// Refresh overview stats and charts
|
|
137
|
-
if (this.dashboard.overviewModule) {
|
|
138
|
-
this.dashboard.overviewModule.loadOverviewStats();
|
|
139
|
-
|
|
140
|
-
if (this.dashboard.currentTab === 'overview') {
|
|
141
|
-
this.dashboard.overviewModule.loadData('overview');
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
handleStatsUpdate() {
|
|
147
|
-
console.log('📊 Stats update requested');
|
|
148
|
-
|
|
149
|
-
// Full refresh
|
|
150
|
-
this.dashboard.loadTabData(this.dashboard.currentTab);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
showNotification(title, message) {
|
|
154
|
-
// Create toast notification
|
|
155
|
-
const toast = document.createElement('div');
|
|
156
|
-
toast.className = 'fixed bottom-4 right-4 bg-purple-600 text-white px-4 py-3 rounded-lg shadow-lg z-50 animate-slide-up';
|
|
157
|
-
toast.innerHTML = `
|
|
158
|
-
<div class="flex items-center gap-2">
|
|
159
|
-
<span class="material-icons text-sm">notifications</span>
|
|
160
|
-
<div>
|
|
161
|
-
<p class="font-semibold text-sm">${title}</p>
|
|
162
|
-
<p class="text-xs opacity-90">${message}</p>
|
|
163
|
-
</div>
|
|
164
|
-
</div>
|
|
165
|
-
`;
|
|
166
|
-
|
|
167
|
-
document.body.appendChild(toast);
|
|
168
|
-
|
|
169
|
-
// Auto remove after 3 seconds
|
|
170
|
-
setTimeout(() => {
|
|
171
|
-
toast.style.opacity = '0';
|
|
172
|
-
toast.style.transform = 'translateY(20px)';
|
|
173
|
-
setTimeout(() => toast.remove(), 300);
|
|
174
|
-
}, 3000);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
updateConnectionStatus(connected) {
|
|
178
|
-
const indicator = document.getElementById('header-ws-status');
|
|
179
|
-
if (!indicator) return;
|
|
180
|
-
|
|
181
|
-
// Keep container classes, only update inner content
|
|
182
|
-
const baseClasses = 'flex items-center gap-1.5 px-3 py-1.5 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700';
|
|
183
|
-
|
|
184
|
-
if (connected) {
|
|
185
|
-
indicator.className = baseClasses;
|
|
186
|
-
indicator.innerHTML = `
|
|
187
|
-
<span class="pulse-dot w-2 h-2 bg-green-500 rounded-full"></span>
|
|
188
|
-
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">Live Updates</span>
|
|
189
|
-
`;
|
|
190
|
-
} else {
|
|
191
|
-
indicator.className = baseClasses;
|
|
192
|
-
indicator.innerHTML = `
|
|
193
|
-
<span class="w-2 h-2 bg-gray-400 rounded-full"></span>
|
|
194
|
-
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">Reconnecting...</span>
|
|
195
|
-
`;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
disconnect() {
|
|
200
|
-
if (this.subscription) {
|
|
201
|
-
this.subscription.unsubscribe();
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (this.centrifuge) {
|
|
205
|
-
this.centrifuge.disconnect();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
console.log('🔌 WebSocket disconnected');
|
|
209
|
-
}
|
|
210
|
-
}
|