@djangocfg/centrifugo 2.1.110 → 2.1.112
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.
- package/package.json +12 -10
- package/src/components/CentrifugoMonitor/CentrifugoMonitor.tsx +13 -4
- package/src/components/CentrifugoMonitor/CentrifugoMonitorDialog.tsx +10 -3
- package/src/components/CentrifugoMonitor/CentrifugoMonitorFAB.tsx +9 -2
- package/src/components/CentrifugoMonitor/CentrifugoMonitorWidget.tsx +12 -3
- package/src/components/ConnectionStatus/ConnectionStatus.tsx +17 -7
- package/src/components/MessagesFeed/MessageFilters.tsx +21 -8
- package/src/components/MessagesFeed/MessagesFeed.tsx +12 -3
- package/src/components/SubscriptionsList/SubscriptionsList.tsx +12 -4
- package/src/debug/DebugPanel/DebugPanel.tsx +18 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/centrifugo",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.112",
|
|
4
4
|
"description": "Production-ready Centrifugo WebSocket client for React with real-time subscriptions, RPC patterns, and connection state management",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"centrifugo",
|
|
@@ -63,10 +63,11 @@
|
|
|
63
63
|
"centrifuge": "^5.2.2"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
66
|
-
"@djangocfg/api": "^2.1.
|
|
67
|
-
"@djangocfg/
|
|
68
|
-
"@djangocfg/ui-
|
|
69
|
-
"@djangocfg/
|
|
66
|
+
"@djangocfg/api": "^2.1.112",
|
|
67
|
+
"@djangocfg/i18n": "^2.1.112",
|
|
68
|
+
"@djangocfg/ui-core": "^2.1.112",
|
|
69
|
+
"@djangocfg/ui-tools": "^2.1.112",
|
|
70
|
+
"@djangocfg/layouts": "^2.1.112",
|
|
70
71
|
"consola": "^3.4.2",
|
|
71
72
|
"lucide-react": "^0.545.0",
|
|
72
73
|
"moment": "^2.30.1",
|
|
@@ -74,11 +75,12 @@
|
|
|
74
75
|
"react-dom": "^19.1.0"
|
|
75
76
|
},
|
|
76
77
|
"devDependencies": {
|
|
77
|
-
"@djangocfg/api": "^2.1.
|
|
78
|
-
"@djangocfg/
|
|
79
|
-
"@djangocfg/
|
|
80
|
-
"@djangocfg/
|
|
81
|
-
"@djangocfg/ui-
|
|
78
|
+
"@djangocfg/api": "^2.1.112",
|
|
79
|
+
"@djangocfg/i18n": "^2.1.112",
|
|
80
|
+
"@djangocfg/layouts": "^2.1.112",
|
|
81
|
+
"@djangocfg/typescript-config": "^2.1.112",
|
|
82
|
+
"@djangocfg/ui-core": "^2.1.112",
|
|
83
|
+
"@djangocfg/ui-tools": "^2.1.112",
|
|
82
84
|
"@types/node": "^24.7.2",
|
|
83
85
|
"@types/react": "^19.1.0",
|
|
84
86
|
"@types/react-dom": "^19.1.0",
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
'use client';
|
|
9
9
|
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { useMemo } from 'react';
|
|
11
11
|
|
|
12
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
12
13
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@djangocfg/ui-core/components';
|
|
13
14
|
|
|
14
15
|
import { ConnectionStatus } from '../ConnectionStatus';
|
|
@@ -54,6 +55,14 @@ export function CentrifugoMonitor({
|
|
|
54
55
|
onSubscriptionClick,
|
|
55
56
|
className = '',
|
|
56
57
|
}: CentrifugoMonitorProps) {
|
|
58
|
+
const t = useTypedT<I18nTranslations>();
|
|
59
|
+
|
|
60
|
+
const labels = useMemo(() => ({
|
|
61
|
+
connection: t('centrifugo.tabs.connection'),
|
|
62
|
+
messages: t('centrifugo.tabs.messages'),
|
|
63
|
+
subscriptions: t('centrifugo.tabs.subscriptions'),
|
|
64
|
+
}), [t]);
|
|
65
|
+
|
|
57
66
|
// Minimal variant - only connection status
|
|
58
67
|
if (variant === 'minimal') {
|
|
59
68
|
return (
|
|
@@ -86,9 +95,9 @@ export function CentrifugoMonitor({
|
|
|
86
95
|
|
|
87
96
|
// Full variant - tabs with all features
|
|
88
97
|
const tabsToShow = [
|
|
89
|
-
showConnectionStatus && { value: 'connection', label:
|
|
90
|
-
showMessagesFeed && { value: 'messages', label:
|
|
91
|
-
showSubscriptions && { value: 'subscriptions', label:
|
|
98
|
+
showConnectionStatus && { value: 'connection', label: labels.connection },
|
|
99
|
+
showMessagesFeed && { value: 'messages', label: labels.messages },
|
|
100
|
+
showSubscriptions && { value: 'subscriptions', label: labels.subscriptions },
|
|
92
101
|
].filter(Boolean) as { value: string; label: string }[];
|
|
93
102
|
|
|
94
103
|
if (tabsToShow.length === 0) {
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
'use client';
|
|
9
9
|
|
|
10
10
|
import { Activity } from 'lucide-react';
|
|
11
|
-
import React, { useState } from 'react';
|
|
11
|
+
import React, { useMemo, useState } from 'react';
|
|
12
12
|
|
|
13
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
13
14
|
import { useEventListener } from '@djangocfg/ui-core';
|
|
14
15
|
import {
|
|
15
16
|
Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle
|
|
@@ -23,9 +24,15 @@ import { CentrifugoMonitor } from './CentrifugoMonitor';
|
|
|
23
24
|
// ─────────────────────────────────────────────────────────────────────────
|
|
24
25
|
|
|
25
26
|
export function CentrifugoMonitorDialog() {
|
|
27
|
+
const t = useTypedT<I18nTranslations>();
|
|
26
28
|
const [open, setOpen] = useState(false);
|
|
27
29
|
const [variant, setVariant] = useState<'compact' | 'full' | 'minimal'>('full');
|
|
28
30
|
|
|
31
|
+
const labels = useMemo(() => ({
|
|
32
|
+
title: t('centrifugo.monitor.title'),
|
|
33
|
+
description: t('centrifugo.monitor.description'),
|
|
34
|
+
}), [t]);
|
|
35
|
+
|
|
29
36
|
// Listen for dialog open event
|
|
30
37
|
useEventListener<typeof CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG, OpenMonitorDialogPayload>(
|
|
31
38
|
CENTRIFUGO_MONITOR_EVENTS.OPEN_MONITOR_DIALOG,
|
|
@@ -58,10 +65,10 @@ export function CentrifugoMonitorDialog() {
|
|
|
58
65
|
<SheetHeader>
|
|
59
66
|
<SheetTitle className="flex items-center gap-2">
|
|
60
67
|
<Activity className="h-5 w-5" />
|
|
61
|
-
|
|
68
|
+
{labels.title}
|
|
62
69
|
</SheetTitle>
|
|
63
70
|
<SheetDescription>
|
|
64
|
-
|
|
71
|
+
{labels.description}
|
|
65
72
|
</SheetDescription>
|
|
66
73
|
</SheetHeader>
|
|
67
74
|
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
'use client';
|
|
9
9
|
|
|
10
10
|
import { Activity } from 'lucide-react';
|
|
11
|
-
import React from 'react';
|
|
11
|
+
import React, { useMemo } from 'react';
|
|
12
|
+
|
|
13
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
12
14
|
|
|
13
15
|
import { emitOpenMonitorDialog, OpenMonitorDialogPayload} from '../../events';
|
|
14
16
|
|
|
@@ -31,6 +33,11 @@ export function CentrifugoMonitorFAB({
|
|
|
31
33
|
size = 'md',
|
|
32
34
|
variant = 'full',
|
|
33
35
|
}: CentrifugoMonitorFABProps) {
|
|
36
|
+
const t = useTypedT<I18nTranslations>();
|
|
37
|
+
|
|
38
|
+
const labels = useMemo(() => ({
|
|
39
|
+
openMonitor: t('centrifugo.monitor.openMonitor'),
|
|
40
|
+
}), [t]);
|
|
34
41
|
|
|
35
42
|
// Position styles
|
|
36
43
|
const positionStyles = {
|
|
@@ -67,7 +74,7 @@ export function CentrifugoMonitorFAB({
|
|
|
67
74
|
...sizeStyles[size],
|
|
68
75
|
zIndex: 9999,
|
|
69
76
|
}}
|
|
70
|
-
aria-label=
|
|
77
|
+
aria-label={labels.openMonitor}
|
|
71
78
|
>
|
|
72
79
|
<Activity className={iconSizes[size]} />
|
|
73
80
|
</button>
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
'use client';
|
|
9
9
|
|
|
10
10
|
import { Activity, Maximize2 } from 'lucide-react';
|
|
11
|
-
import React from 'react';
|
|
11
|
+
import React, { useMemo } from 'react';
|
|
12
12
|
|
|
13
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
13
14
|
import { Button, Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui-core/components';
|
|
14
15
|
|
|
15
16
|
import { emitOpenMonitorDialog } from '../../events';
|
|
@@ -30,10 +31,18 @@ export interface CentrifugoMonitorWidgetProps {
|
|
|
30
31
|
// ─────────────────────────────────────────────────────────────────────────
|
|
31
32
|
|
|
32
33
|
export function CentrifugoMonitorWidget({
|
|
33
|
-
title
|
|
34
|
+
title,
|
|
34
35
|
showExpandButton = true,
|
|
35
36
|
className = '',
|
|
36
37
|
}: CentrifugoMonitorWidgetProps) {
|
|
38
|
+
const t = useTypedT<I18nTranslations>();
|
|
39
|
+
|
|
40
|
+
const labels = useMemo(() => ({
|
|
41
|
+
widgetTitle: t('centrifugo.monitor.widgetTitle'),
|
|
42
|
+
}), [t]);
|
|
43
|
+
|
|
44
|
+
const displayTitle = title ?? labels.widgetTitle;
|
|
45
|
+
|
|
37
46
|
const handleExpand = () => {
|
|
38
47
|
emitOpenMonitorDialog({ variant: 'full' });
|
|
39
48
|
};
|
|
@@ -43,7 +52,7 @@ export function CentrifugoMonitorWidget({
|
|
|
43
52
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
44
53
|
<CardTitle className="text-sm font-medium flex items-center gap-2">
|
|
45
54
|
<Activity className="h-4 w-4" />
|
|
46
|
-
{
|
|
55
|
+
{displayTitle}
|
|
47
56
|
</CardTitle>
|
|
48
57
|
{showExpandButton && (
|
|
49
58
|
<Button
|
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { Clock, Radio, Wifi, WifiOff } from 'lucide-react';
|
|
11
11
|
import moment from 'moment';
|
|
12
|
-
import React, { useEffect, useState } from 'react';
|
|
12
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
13
13
|
|
|
14
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
14
15
|
import { Badge } from '@djangocfg/ui-core/components';
|
|
15
16
|
|
|
16
17
|
import { getConsolaLogger } from '../../core/logger/consolaLogger';
|
|
@@ -39,11 +40,20 @@ export function ConnectionStatus({
|
|
|
39
40
|
showSubscriptions = false,
|
|
40
41
|
className = '',
|
|
41
42
|
}: ConnectionStatusProps) {
|
|
43
|
+
const t = useTypedT<I18nTranslations>();
|
|
42
44
|
const { isConnected, client } = useCentrifugo();
|
|
43
45
|
const [connectionTime, setConnectionTime] = useState<moment.Moment | null>(null);
|
|
44
46
|
const [uptime, setUptime] = useState<string>('');
|
|
45
47
|
const [activeSubscriptions, setActiveSubscriptions] = useState<number>(0);
|
|
46
48
|
|
|
49
|
+
const labels = useMemo(() => ({
|
|
50
|
+
connected: t('centrifugo.status.connected'),
|
|
51
|
+
disconnected: t('centrifugo.status.disconnected'),
|
|
52
|
+
uptime: t('centrifugo.status.uptime'),
|
|
53
|
+
subscriptions: t('centrifugo.status.subscriptions'),
|
|
54
|
+
realtimeUnavailable: t('centrifugo.status.realtimeUnavailable'),
|
|
55
|
+
}), [t]);
|
|
56
|
+
|
|
47
57
|
// Track connection time
|
|
48
58
|
useEffect(() => {
|
|
49
59
|
if (isConnected && !connectionTime) {
|
|
@@ -114,7 +124,7 @@ export function ConnectionStatus({
|
|
|
114
124
|
className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''} ${className}`}
|
|
115
125
|
>
|
|
116
126
|
<span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
|
|
117
|
-
{isConnected ?
|
|
127
|
+
{isConnected ? labels.connected : labels.disconnected}
|
|
118
128
|
</Badge>
|
|
119
129
|
);
|
|
120
130
|
}
|
|
@@ -129,7 +139,7 @@ export function ConnectionStatus({
|
|
|
129
139
|
<WifiOff className="h-4 w-4 text-red-600" />
|
|
130
140
|
)}
|
|
131
141
|
<span className="text-sm font-medium">
|
|
132
|
-
{isConnected ?
|
|
142
|
+
{isConnected ? labels.connected : labels.disconnected}
|
|
133
143
|
</span>
|
|
134
144
|
{showUptime && uptime && (
|
|
135
145
|
<span className="text-xs text-muted-foreground">({uptime})</span>
|
|
@@ -154,7 +164,7 @@ export function ConnectionStatus({
|
|
|
154
164
|
className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''}`}
|
|
155
165
|
>
|
|
156
166
|
<span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
|
|
157
|
-
{isConnected ?
|
|
167
|
+
{isConnected ? labels.connected : labels.disconnected}
|
|
158
168
|
</Badge>
|
|
159
169
|
</div>
|
|
160
170
|
|
|
@@ -166,7 +176,7 @@ export function ConnectionStatus({
|
|
|
166
176
|
<div className="flex items-center justify-between text-xs">
|
|
167
177
|
<span className="text-muted-foreground flex items-center gap-1">
|
|
168
178
|
<Clock className="h-3 w-3" />
|
|
169
|
-
|
|
179
|
+
{labels.uptime}
|
|
170
180
|
</span>
|
|
171
181
|
<span className="font-mono font-medium">{uptime}</span>
|
|
172
182
|
</div>
|
|
@@ -177,7 +187,7 @@ export function ConnectionStatus({
|
|
|
177
187
|
<div className="flex items-center justify-between text-xs">
|
|
178
188
|
<span className="text-muted-foreground flex items-center gap-1">
|
|
179
189
|
<Radio className="h-3 w-3" />
|
|
180
|
-
|
|
190
|
+
{labels.subscriptions}
|
|
181
191
|
</span>
|
|
182
192
|
<span className="font-mono font-medium">{activeSubscriptions}</span>
|
|
183
193
|
</div>
|
|
@@ -185,7 +195,7 @@ export function ConnectionStatus({
|
|
|
185
195
|
</div>
|
|
186
196
|
) : (
|
|
187
197
|
<div className="text-xs text-muted-foreground p-2 rounded bg-red-50 dark:bg-red-950/20">
|
|
188
|
-
|
|
198
|
+
{labels.realtimeUnavailable}
|
|
189
199
|
</div>
|
|
190
200
|
)}
|
|
191
201
|
</div>
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
'use client';
|
|
8
8
|
|
|
9
9
|
import { Filter, Search, X } from 'lucide-react';
|
|
10
|
-
import React from 'react';
|
|
10
|
+
import React, { useMemo } from 'react';
|
|
11
11
|
|
|
12
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
12
13
|
import { Badge, Button, Input } from '@djangocfg/ui-core/components';
|
|
13
14
|
|
|
14
15
|
import type { MessageFilters as MessageFiltersType } from './types';
|
|
@@ -34,6 +35,18 @@ export function MessageFilters({
|
|
|
34
35
|
autoScroll,
|
|
35
36
|
onAutoScrollChange,
|
|
36
37
|
}: MessageFiltersProps) {
|
|
38
|
+
const t = useTypedT<I18nTranslations>();
|
|
39
|
+
|
|
40
|
+
const labels = useMemo(() => ({
|
|
41
|
+
title: t('centrifugo.filters.title'),
|
|
42
|
+
active: t('centrifugo.filters.active'),
|
|
43
|
+
clear: t('centrifugo.filters.clear'),
|
|
44
|
+
searchMessages: t('centrifugo.filters.searchMessages'),
|
|
45
|
+
level: t('centrifugo.filters.level'),
|
|
46
|
+
type: t('centrifugo.filters.type'),
|
|
47
|
+
autoScrollToLatest: t('centrifugo.filters.autoScrollToLatest'),
|
|
48
|
+
}), [t]);
|
|
49
|
+
|
|
37
50
|
const hasActiveFilters =
|
|
38
51
|
(filters.channels && filters.channels.length > 0) ||
|
|
39
52
|
(filters.types && filters.types.length > 0) ||
|
|
@@ -78,17 +91,17 @@ export function MessageFilters({
|
|
|
78
91
|
<div className="flex items-center justify-between">
|
|
79
92
|
<div className="flex items-center gap-2">
|
|
80
93
|
<Filter className="h-4 w-4 text-muted-foreground" />
|
|
81
|
-
<span className="text-sm font-medium">
|
|
94
|
+
<span className="text-sm font-medium">{labels.title}</span>
|
|
82
95
|
{hasActiveFilters && (
|
|
83
96
|
<Badge variant="secondary" className="text-xs">
|
|
84
|
-
|
|
97
|
+
{labels.active}
|
|
85
98
|
</Badge>
|
|
86
99
|
)}
|
|
87
100
|
</div>
|
|
88
101
|
{hasActiveFilters && (
|
|
89
102
|
<Button size="sm" variant="ghost" onClick={handleClearFilters}>
|
|
90
103
|
<X className="h-3 w-3 mr-1" />
|
|
91
|
-
|
|
104
|
+
{labels.clear}
|
|
92
105
|
</Button>
|
|
93
106
|
)}
|
|
94
107
|
</div>
|
|
@@ -98,7 +111,7 @@ export function MessageFilters({
|
|
|
98
111
|
<Search className="h-4 w-4 text-muted-foreground" />
|
|
99
112
|
<Input
|
|
100
113
|
type="text"
|
|
101
|
-
placeholder=
|
|
114
|
+
placeholder={labels.searchMessages}
|
|
102
115
|
value={filters.searchQuery || ''}
|
|
103
116
|
onChange={handleSearchChange}
|
|
104
117
|
className="flex-1"
|
|
@@ -107,7 +120,7 @@ export function MessageFilters({
|
|
|
107
120
|
|
|
108
121
|
{/* Level Filters */}
|
|
109
122
|
<div className="space-y-2">
|
|
110
|
-
<span className="text-xs text-muted-foreground">
|
|
123
|
+
<span className="text-xs text-muted-foreground">{labels.level}</span>
|
|
111
124
|
<div className="flex flex-wrap gap-2">
|
|
112
125
|
{(['info', 'success', 'warning', 'error'] as const).map((level) => {
|
|
113
126
|
const isActive = filters.levels?.includes(level);
|
|
@@ -127,7 +140,7 @@ export function MessageFilters({
|
|
|
127
140
|
|
|
128
141
|
{/* Type Filters */}
|
|
129
142
|
<div className="space-y-2">
|
|
130
|
-
<span className="text-xs text-muted-foreground">
|
|
143
|
+
<span className="text-xs text-muted-foreground">{labels.type}</span>
|
|
131
144
|
<div className="flex flex-wrap gap-2">
|
|
132
145
|
{(['connection', 'subscription', 'publication', 'unsubscription', 'error', 'system'] as const).map((type) => {
|
|
133
146
|
const isActive = filters.types?.includes(type);
|
|
@@ -155,7 +168,7 @@ export function MessageFilters({
|
|
|
155
168
|
onChange={(e) => onAutoScrollChange(e.target.checked)}
|
|
156
169
|
className="h-4 w-4 rounded border-gray-300"
|
|
157
170
|
/>
|
|
158
|
-
<span>
|
|
171
|
+
<span>{labels.autoScrollToLatest}</span>
|
|
159
172
|
</label>
|
|
160
173
|
</div>
|
|
161
174
|
)}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import moment from 'moment';
|
|
14
14
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
|
|
16
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
16
17
|
import {
|
|
17
18
|
Badge, Button, Card, CardContent, CardHeader, CardTitle, ScrollArea
|
|
18
19
|
} from '@djangocfg/ui-core/components';
|
|
@@ -35,7 +36,15 @@ export function MessagesFeed({
|
|
|
35
36
|
onMessageClick,
|
|
36
37
|
className = '',
|
|
37
38
|
}: MessagesFeedProps) {
|
|
39
|
+
const t = useTypedT<I18nTranslations>();
|
|
38
40
|
const { isConnected, client } = useCentrifugo();
|
|
41
|
+
|
|
42
|
+
const labels = useMemo(() => ({
|
|
43
|
+
title: t('centrifugo.feed.title'),
|
|
44
|
+
noMessages: t('centrifugo.feed.noMessages'),
|
|
45
|
+
pausedClickToResume: t('centrifugo.feed.pausedClickToResume'),
|
|
46
|
+
viewData: t('centrifugo.feed.viewData'),
|
|
47
|
+
}), [t]);
|
|
39
48
|
const [messages, setMessages] = useState<CentrifugoMessage[]>([]);
|
|
40
49
|
const [isPaused, setIsPaused] = useState(false);
|
|
41
50
|
const [autoScroll, setAutoScroll] = useState(initialAutoScroll);
|
|
@@ -273,7 +282,7 @@ export function MessagesFeed({
|
|
|
273
282
|
<div className="flex items-center justify-between">
|
|
274
283
|
<CardTitle className="flex items-center gap-2">
|
|
275
284
|
<Activity className="h-5 w-5" />
|
|
276
|
-
|
|
285
|
+
{labels.title}
|
|
277
286
|
<Badge variant="outline">{filteredMessages.length}</Badge>
|
|
278
287
|
</CardTitle>
|
|
279
288
|
|
|
@@ -326,7 +335,7 @@ export function MessagesFeed({
|
|
|
326
335
|
<div className="flex flex-col items-center justify-center py-12 text-center">
|
|
327
336
|
<Activity className="h-12 w-12 text-muted-foreground mb-4" />
|
|
328
337
|
<p className="text-sm text-muted-foreground">
|
|
329
|
-
{isPaused ?
|
|
338
|
+
{isPaused ? labels.pausedClickToResume : labels.noMessages}
|
|
330
339
|
</p>
|
|
331
340
|
</div>
|
|
332
341
|
) : (
|
|
@@ -359,7 +368,7 @@ export function MessagesFeed({
|
|
|
359
368
|
{msg.formattedData && (
|
|
360
369
|
<details className="text-xs">
|
|
361
370
|
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
|
362
|
-
|
|
371
|
+
{labels.viewData}
|
|
363
372
|
</summary>
|
|
364
373
|
<pre className="mt-2 p-2 bg-muted rounded overflow-x-auto">
|
|
365
374
|
{msg.formattedData}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
import { Subscription, SubscriptionState } from 'centrifuge';
|
|
10
10
|
import { Radio, RefreshCw, Trash2 } from 'lucide-react';
|
|
11
|
-
import React, { useEffect, useState } from 'react';
|
|
11
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
12
12
|
|
|
13
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
13
14
|
import {
|
|
14
15
|
Badge, Button, Card, CardContent, CardHeader, CardTitle, ScrollArea
|
|
15
16
|
} from '@djangocfg/ui-core/components';
|
|
@@ -43,9 +44,16 @@ export function SubscriptionsList({
|
|
|
43
44
|
onSubscriptionClick,
|
|
44
45
|
className = '',
|
|
45
46
|
}: SubscriptionsListProps) {
|
|
47
|
+
const t = useTypedT<I18nTranslations>();
|
|
46
48
|
const { isConnected, client } = useCentrifugo();
|
|
47
49
|
const [subscriptions, setSubscriptions] = useState<SubscriptionItem[]>([]);
|
|
48
50
|
|
|
51
|
+
const labels = useMemo(() => ({
|
|
52
|
+
title: t('centrifugo.subscriptionsList.title'),
|
|
53
|
+
notConnected: t('centrifugo.subscriptionsList.notConnected'),
|
|
54
|
+
noActiveSubscriptions: t('centrifugo.subscriptionsList.noActiveSubscriptions'),
|
|
55
|
+
}), [t]);
|
|
56
|
+
|
|
49
57
|
// Update subscriptions list
|
|
50
58
|
const updateSubscriptions = () => {
|
|
51
59
|
if (!client || !isConnected) {
|
|
@@ -113,7 +121,7 @@ export function SubscriptionsList({
|
|
|
113
121
|
<div className="flex items-center justify-between">
|
|
114
122
|
<CardTitle className="flex items-center gap-2">
|
|
115
123
|
<Radio className="h-5 w-5" />
|
|
116
|
-
|
|
124
|
+
{labels.title}
|
|
117
125
|
<Badge variant="outline">{subscriptions.length}</Badge>
|
|
118
126
|
</CardTitle>
|
|
119
127
|
|
|
@@ -128,11 +136,11 @@ export function SubscriptionsList({
|
|
|
128
136
|
<CardContent>
|
|
129
137
|
{!isConnected ? (
|
|
130
138
|
<div className="text-center py-8 text-sm text-muted-foreground">
|
|
131
|
-
|
|
139
|
+
{labels.notConnected}
|
|
132
140
|
</div>
|
|
133
141
|
) : subscriptions.length === 0 ? (
|
|
134
142
|
<div className="text-center py-8 text-sm text-muted-foreground">
|
|
135
|
-
|
|
143
|
+
{labels.noActiveSubscriptions}
|
|
136
144
|
</div>
|
|
137
145
|
) : (
|
|
138
146
|
<ScrollArea className="h-[300px]">
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
'use client';
|
|
9
9
|
|
|
10
10
|
import { Bug } from 'lucide-react';
|
|
11
|
-
import { useState } from 'react';
|
|
11
|
+
import { useMemo, useState } from 'react';
|
|
12
12
|
|
|
13
|
+
import { useTypedT, type I18nTranslations } from '@djangocfg/i18n';
|
|
13
14
|
import {
|
|
14
15
|
Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Tabs, TabsContent, TabsList,
|
|
15
16
|
TabsTrigger
|
|
@@ -24,9 +25,19 @@ import { SubscriptionsTab } from '../SubscriptionsTab';
|
|
|
24
25
|
// ─────────────────────────────────────────────────────────────────────────
|
|
25
26
|
|
|
26
27
|
export function DebugPanel() {
|
|
28
|
+
const t = useTypedT<I18nTranslations>();
|
|
27
29
|
const [isOpen, setIsOpen] = useState(false);
|
|
28
30
|
const [activeTab, setActiveTab] = useState('connection');
|
|
29
31
|
|
|
32
|
+
const labels = useMemo(() => ({
|
|
33
|
+
title: t('centrifugo.debug.title'),
|
|
34
|
+
description: t('centrifugo.debug.description'),
|
|
35
|
+
openDebugPanel: t('centrifugo.debug.openDebugPanel'),
|
|
36
|
+
tabConnection: t('centrifugo.debug.tabConnection'),
|
|
37
|
+
tabLogs: t('centrifugo.debug.tabLogs'),
|
|
38
|
+
tabSubscriptions: t('centrifugo.debug.tabSubscriptions'),
|
|
39
|
+
}), [t]);
|
|
40
|
+
|
|
30
41
|
return (
|
|
31
42
|
<>
|
|
32
43
|
{/* FAB Button (fixed bottom-left) */}
|
|
@@ -41,7 +52,7 @@ export function DebugPanel() {
|
|
|
41
52
|
height: '56px',
|
|
42
53
|
zIndex: 9999,
|
|
43
54
|
}}
|
|
44
|
-
aria-label=
|
|
55
|
+
aria-label={labels.openDebugPanel}
|
|
45
56
|
>
|
|
46
57
|
<Bug className="h-6 w-6" />
|
|
47
58
|
</button>
|
|
@@ -50,18 +61,18 @@ export function DebugPanel() {
|
|
|
50
61
|
<Sheet open={isOpen} onOpenChange={setIsOpen}>
|
|
51
62
|
<SheetContent side="right" className="w-full sm:max-w-2xl">
|
|
52
63
|
<SheetHeader>
|
|
53
|
-
<SheetTitle>
|
|
64
|
+
<SheetTitle>{labels.title}</SheetTitle>
|
|
54
65
|
<SheetDescription>
|
|
55
|
-
|
|
66
|
+
{labels.description}
|
|
56
67
|
</SheetDescription>
|
|
57
68
|
</SheetHeader>
|
|
58
69
|
|
|
59
70
|
{/* Tabs */}
|
|
60
71
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="mt-6">
|
|
61
72
|
<TabsList className="grid w-full grid-cols-3">
|
|
62
|
-
<TabsTrigger value="connection">
|
|
63
|
-
<TabsTrigger value="logs">
|
|
64
|
-
<TabsTrigger value="subscriptions">
|
|
73
|
+
<TabsTrigger value="connection">{labels.tabConnection}</TabsTrigger>
|
|
74
|
+
<TabsTrigger value="logs">{labels.tabLogs}</TabsTrigger>
|
|
75
|
+
<TabsTrigger value="subscriptions">{labels.tabSubscriptions}</TabsTrigger>
|
|
65
76
|
</TabsList>
|
|
66
77
|
|
|
67
78
|
<TabsContent value="connection" className="mt-4">
|