@djangocfg/centrifugo 1.0.3 → 1.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@djangocfg/centrifugo",
3
- "version": "1.0.3",
4
- "description": "WebSocket RPC client for Django-CFG + Centrifugo integration",
3
+ "version": "1.0.4",
4
+ "description": "Production-ready Centrifugo WebSocket client with React integration, RPC pattern, composable UI components, and comprehensive monitoring tools",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
7
7
  "exports": {
@@ -19,8 +19,8 @@
19
19
  "centrifuge": "^5.2.2"
20
20
  },
21
21
  "peerDependencies": {
22
- "@djangocfg/ui": "^1.2.28",
23
- "@djangocfg/layouts": "^1.2.28",
22
+ "@djangocfg/ui": "^1.2.29",
23
+ "@djangocfg/layouts": "^1.2.29",
24
24
  "consola": "^3.4.2",
25
25
  "lucide-react": "^0.468.0",
26
26
  "moment": "^2.30.1",
@@ -28,7 +28,7 @@
28
28
  "react-dom": "^19.0.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@djangocfg/typescript-config": "^1.2.28",
31
+ "@djangocfg/typescript-config": "^1.2.29",
32
32
  "@types/react": "^19.0.6",
33
33
  "@types/react-dom": "^19.0.2",
34
34
  "moment": "^2.30.1",
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Centrifugo Monitor Component
3
+ *
4
+ * Universal monitoring component that can be embedded anywhere
5
+ * Combines ConnectionStatus, MessagesFeed, and SubscriptionsList
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React from 'react';
11
+ import { Tabs, TabsList, TabsTrigger, TabsContent } from '@djangocfg/ui';
12
+ import { ConnectionStatus } from '../ConnectionStatus';
13
+ import { MessagesFeed } from '../MessagesFeed';
14
+ import { SubscriptionsList } from '../SubscriptionsList';
15
+ import type { CentrifugoMessage } from '../MessagesFeed';
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────
18
+ // Types
19
+ // ─────────────────────────────────────────────────────────────────────────
20
+
21
+ export interface CentrifugoMonitorProps {
22
+ variant?: 'compact' | 'full' | 'minimal';
23
+ showConnectionStatus?: boolean;
24
+ showMessagesFeed?: boolean;
25
+ showSubscriptions?: boolean;
26
+ showFilters?: boolean;
27
+ showControls?: boolean;
28
+ maxMessages?: number;
29
+ channels?: string[]; // Pre-filter by channels
30
+ autoScroll?: boolean;
31
+ onMessageClick?: (message: CentrifugoMessage) => void;
32
+ onSubscriptionClick?: (channel: string) => void;
33
+ className?: string;
34
+ }
35
+
36
+ // ─────────────────────────────────────────────────────────────────────────
37
+ // Component
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+
40
+ export function CentrifugoMonitor({
41
+ variant = 'full',
42
+ showConnectionStatus = true,
43
+ showMessagesFeed = true,
44
+ showSubscriptions = true,
45
+ showFilters = true,
46
+ showControls = true,
47
+ maxMessages = 100,
48
+ channels = [],
49
+ autoScroll = true,
50
+ onMessageClick,
51
+ onSubscriptionClick,
52
+ className = '',
53
+ }: CentrifugoMonitorProps) {
54
+ // Minimal variant - only connection status
55
+ if (variant === 'minimal') {
56
+ return (
57
+ <div className={className}>
58
+ <ConnectionStatus variant="detailed" showUptime showSubscriptions />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ // Compact variant - connection status + messages feed
64
+ if (variant === 'compact') {
65
+ return (
66
+ <div className={`space-y-4 ${className}`}>
67
+ {showConnectionStatus && (
68
+ <ConnectionStatus variant="detailed" showUptime showSubscriptions />
69
+ )}
70
+ {showMessagesFeed && (
71
+ <MessagesFeed
72
+ maxMessages={maxMessages}
73
+ showFilters={false}
74
+ showControls={showControls}
75
+ channels={channels}
76
+ autoScroll={autoScroll}
77
+ onMessageClick={onMessageClick}
78
+ />
79
+ )}
80
+ </div>
81
+ );
82
+ }
83
+
84
+ // Full variant - tabs with all features
85
+ const tabsToShow = [
86
+ showConnectionStatus && { value: 'connection', label: 'Connection' },
87
+ showMessagesFeed && { value: 'messages', label: 'Messages' },
88
+ showSubscriptions && { value: 'subscriptions', label: 'Subscriptions' },
89
+ ].filter(Boolean) as { value: string; label: string }[];
90
+
91
+ if (tabsToShow.length === 0) {
92
+ return null;
93
+ }
94
+
95
+ return (
96
+ <div className={className}>
97
+ <Tabs defaultValue={tabsToShow[0].value}>
98
+ <TabsList className={`grid w-full grid-cols-${tabsToShow.length}`}>
99
+ {tabsToShow.map((tab) => (
100
+ <TabsTrigger key={tab.value} value={tab.value}>
101
+ {tab.label}
102
+ </TabsTrigger>
103
+ ))}
104
+ </TabsList>
105
+
106
+ {showConnectionStatus && (
107
+ <TabsContent value="connection" className="mt-4">
108
+ <ConnectionStatus variant="detailed" showUptime showSubscriptions />
109
+ </TabsContent>
110
+ )}
111
+
112
+ {showMessagesFeed && (
113
+ <TabsContent value="messages" className="mt-4">
114
+ <MessagesFeed
115
+ maxMessages={maxMessages}
116
+ showFilters={showFilters}
117
+ showControls={showControls}
118
+ channels={channels}
119
+ autoScroll={autoScroll}
120
+ onMessageClick={onMessageClick}
121
+ />
122
+ </TabsContent>
123
+ )}
124
+
125
+ {showSubscriptions && (
126
+ <TabsContent value="subscriptions" className="mt-4">
127
+ <SubscriptionsList
128
+ showControls={showControls}
129
+ onSubscriptionClick={onSubscriptionClick}
130
+ />
131
+ </TabsContent>
132
+ )}
133
+ </Tabs>
134
+ </div>
135
+ );
136
+ }
137
+
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Centrifugo Monitor Dialog Component
3
+ *
4
+ * Sheet/Dialog wrapper for CentrifugoMonitor
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import {
11
+ Sheet,
12
+ SheetContent,
13
+ SheetHeader,
14
+ SheetTitle,
15
+ SheetDescription,
16
+ } from '@djangocfg/ui';
17
+ import { Activity } from 'lucide-react';
18
+ import { CentrifugoMonitor, type CentrifugoMonitorProps } from './CentrifugoMonitor';
19
+
20
+ // ─────────────────────────────────────────────────────────────────────────
21
+ // Types
22
+ // ─────────────────────────────────────────────────────────────────────────
23
+
24
+ export interface CentrifugoMonitorDialogProps extends Omit<CentrifugoMonitorProps, 'className'> {
25
+ open: boolean;
26
+ onOpenChange: (open: boolean) => void;
27
+ title?: string;
28
+ description?: string;
29
+ side?: 'left' | 'right' | 'top' | 'bottom';
30
+ className?: string;
31
+ }
32
+
33
+ // ─────────────────────────────────────────────────────────────────────────
34
+ // Component
35
+ // ─────────────────────────────────────────────────────────────────────────
36
+
37
+ export function CentrifugoMonitorDialog({
38
+ open,
39
+ onOpenChange,
40
+ title = 'Centrifugo Monitor',
41
+ description = 'Real-time WebSocket monitoring and debugging',
42
+ side = 'right',
43
+ className = '',
44
+ ...monitorProps
45
+ }: CentrifugoMonitorDialogProps) {
46
+ return (
47
+ <Sheet open={open} onOpenChange={onOpenChange}>
48
+ <SheetContent side={side} className={`w-full sm:max-w-2xl ${className}`}>
49
+ <SheetHeader>
50
+ <SheetTitle className="flex items-center gap-2">
51
+ <Activity className="h-5 w-5" />
52
+ {title}
53
+ </SheetTitle>
54
+ {description && <SheetDescription>{description}</SheetDescription>}
55
+ </SheetHeader>
56
+
57
+ <div className="mt-6">
58
+ <CentrifugoMonitor {...monitorProps} />
59
+ </div>
60
+ </SheetContent>
61
+ </Sheet>
62
+ );
63
+ }
64
+
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Centrifugo Monitor FAB Component
3
+ *
4
+ * Floating Action Button that opens CentrifugoMonitorDialog
5
+ * Replaces the old DebugPanel FAB
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { useState } from 'react';
11
+ import { Activity } from 'lucide-react';
12
+ import { CentrifugoMonitorDialog, type CentrifugoMonitorDialogProps } from './CentrifugoMonitorDialog';
13
+
14
+ // ─────────────────────────────────────────────────────────────────────────
15
+ // Types
16
+ // ─────────────────────────────────────────────────────────────────────────
17
+
18
+ export interface CentrifugoMonitorFABProps extends Omit<CentrifugoMonitorDialogProps, 'open' | 'onOpenChange'> {
19
+ position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
20
+ size?: 'sm' | 'md' | 'lg';
21
+ }
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────
24
+ // Component
25
+ // ─────────────────────────────────────────────────────────────────────────
26
+
27
+ export function CentrifugoMonitorFAB({
28
+ position = 'bottom-left',
29
+ size = 'md',
30
+ ...dialogProps
31
+ }: CentrifugoMonitorFABProps) {
32
+ const [isOpen, setIsOpen] = useState(false);
33
+
34
+ // Position styles
35
+ const positionStyles = {
36
+ 'bottom-left': { bottom: '1rem', left: '1rem' },
37
+ 'bottom-right': { bottom: '1rem', right: '1rem' },
38
+ 'top-left': { top: '1rem', left: '1rem' },
39
+ 'top-right': { top: '1rem', right: '1rem' },
40
+ };
41
+
42
+ // Size styles
43
+ const sizeStyles = {
44
+ sm: { width: '48px', height: '48px' },
45
+ md: { width: '56px', height: '56px' },
46
+ lg: { width: '64px', height: '64px' },
47
+ };
48
+
49
+ const iconSizes = {
50
+ sm: 'h-5 w-5',
51
+ md: 'h-6 w-6',
52
+ lg: 'h-7 w-7',
53
+ };
54
+
55
+ return (
56
+ <>
57
+ {/* FAB Button */}
58
+ <button
59
+ onClick={() => setIsOpen(true)}
60
+ className="rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all duration-200 flex items-center justify-center hover:scale-110"
61
+ style={{
62
+ position: 'fixed',
63
+ ...positionStyles[position],
64
+ ...sizeStyles[size],
65
+ zIndex: 9999,
66
+ }}
67
+ aria-label="Open Centrifugo Monitor"
68
+ >
69
+ <Activity className={iconSizes[size]} />
70
+ </button>
71
+
72
+ {/* Dialog */}
73
+ <CentrifugoMonitorDialog
74
+ open={isOpen}
75
+ onOpenChange={setIsOpen}
76
+ {...dialogProps}
77
+ />
78
+ </>
79
+ );
80
+ }
81
+
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Centrifugo Monitor Widget Component
3
+ *
4
+ * Card-based widget for dashboards
5
+ * Combines ConnectionStatusCard with compact monitor view
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { useState } from 'react';
11
+ import { Card, CardContent, CardHeader, CardTitle, Button } from '@djangocfg/ui';
12
+ import { Activity, Maximize2 } from 'lucide-react';
13
+ import { ConnectionStatus } from '../ConnectionStatus';
14
+ import { CentrifugoMonitorDialog } from './CentrifugoMonitorDialog';
15
+ import type { CentrifugoMonitorProps } from './CentrifugoMonitor';
16
+
17
+ // ─────────────────────────────────────────────────────────────────────────
18
+ // Types
19
+ // ─────────────────────────────────────────────────────────────────────────
20
+
21
+ export interface CentrifugoMonitorWidgetProps extends Omit<CentrifugoMonitorProps, 'variant' | 'className'> {
22
+ title?: string;
23
+ showExpandButton?: boolean;
24
+ className?: string;
25
+ }
26
+
27
+ // ─────────────────────────────────────────────────────────────────────────
28
+ // Component
29
+ // ─────────────────────────────────────────────────────────────────────────
30
+
31
+ export function CentrifugoMonitorWidget({
32
+ title = 'WebSocket Monitor',
33
+ showExpandButton = true,
34
+ className = '',
35
+ ...monitorProps
36
+ }: CentrifugoMonitorWidgetProps) {
37
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
38
+
39
+ return (
40
+ <>
41
+ <Card className={className}>
42
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
43
+ <CardTitle className="text-sm font-medium flex items-center gap-2">
44
+ <Activity className="h-4 w-4" />
45
+ {title}
46
+ </CardTitle>
47
+ {showExpandButton && (
48
+ <Button
49
+ size="sm"
50
+ variant="ghost"
51
+ onClick={() => setIsDialogOpen(true)}
52
+ >
53
+ <Maximize2 className="h-4 w-4" />
54
+ </Button>
55
+ )}
56
+ </CardHeader>
57
+ <CardContent>
58
+ <ConnectionStatus variant="detailed" showUptime showSubscriptions />
59
+ </CardContent>
60
+ </Card>
61
+
62
+ {/* Full monitor dialog */}
63
+ {showExpandButton && (
64
+ <CentrifugoMonitorDialog
65
+ open={isDialogOpen}
66
+ onOpenChange={setIsDialogOpen}
67
+ variant="full"
68
+ {...monitorProps}
69
+ />
70
+ )}
71
+ </>
72
+ );
73
+ }
74
+
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Centrifugo Monitor Components
3
+ */
4
+
5
+ export { CentrifugoMonitor } from './CentrifugoMonitor';
6
+ export { CentrifugoMonitorDialog } from './CentrifugoMonitorDialog';
7
+ export { CentrifugoMonitorFAB } from './CentrifugoMonitorFAB';
8
+ export { CentrifugoMonitorWidget } from './CentrifugoMonitorWidget';
9
+
10
+ export type { CentrifugoMonitorProps } from './CentrifugoMonitor';
11
+ export type { CentrifugoMonitorDialogProps } from './CentrifugoMonitorDialog';
12
+ export type { CentrifugoMonitorFABProps } from './CentrifugoMonitorFAB';
13
+ export type { CentrifugoMonitorWidgetProps } from './CentrifugoMonitorWidget';
14
+
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Connection Status Component
3
+ *
4
+ * Universal component for displaying Centrifugo connection status
5
+ * Supports multiple variants: badge, inline, card, detailed
6
+ */
7
+
8
+ 'use client';
9
+
10
+ import React, { useState, useEffect } from 'react';
11
+ import { Badge } from '@djangocfg/ui';
12
+ import { Wifi, WifiOff, Radio, Clock } from 'lucide-react';
13
+ import moment from 'moment';
14
+ import { useCentrifugo } from '../../providers/CentrifugoProvider';
15
+ import { createLogger } from '../../core/logger/createLogger';
16
+
17
+ const logger = createLogger('ConnectionStatus');
18
+
19
+ // ─────────────────────────────────────────────────────────────────────────
20
+ // Types
21
+ // ─────────────────────────────────────────────────────────────────────────
22
+
23
+ export interface ConnectionStatusProps {
24
+ variant?: 'badge' | 'inline' | 'detailed';
25
+ showUptime?: boolean;
26
+ showSubscriptions?: boolean;
27
+ className?: string;
28
+ }
29
+
30
+ // ─────────────────────────────────────────────────────────────────────────
31
+ // Component
32
+ // ─────────────────────────────────────────────────────────────────────────
33
+
34
+ export function ConnectionStatus({
35
+ variant = 'badge',
36
+ showUptime = false,
37
+ showSubscriptions = false,
38
+ className = '',
39
+ }: ConnectionStatusProps) {
40
+ const { isConnected, client } = useCentrifugo();
41
+ const [connectionTime, setConnectionTime] = useState<moment.Moment | null>(null);
42
+ const [uptime, setUptime] = useState<string>('');
43
+ const [activeSubscriptions, setActiveSubscriptions] = useState<number>(0);
44
+
45
+ // Track connection time
46
+ useEffect(() => {
47
+ if (isConnected && !connectionTime) {
48
+ setConnectionTime(moment.utc());
49
+ } else if (!isConnected) {
50
+ setConnectionTime(null);
51
+ }
52
+ }, [isConnected, connectionTime]);
53
+
54
+ // Update uptime every second
55
+ useEffect(() => {
56
+ if (!isConnected || !connectionTime) {
57
+ setUptime('');
58
+ return;
59
+ }
60
+
61
+ const updateUptime = () => {
62
+ const now = moment.utc();
63
+ const duration = moment.duration(now.diff(connectionTime));
64
+
65
+ const hours = Math.floor(duration.asHours());
66
+ const minutes = duration.minutes();
67
+ const seconds = duration.seconds();
68
+
69
+ if (hours > 0) {
70
+ setUptime(`${hours}h ${minutes}m`);
71
+ } else if (minutes > 0) {
72
+ setUptime(`${minutes}m ${seconds}s`);
73
+ } else {
74
+ setUptime(`${seconds}s`);
75
+ }
76
+ };
77
+
78
+ updateUptime();
79
+ const interval = setInterval(updateUptime, 1000);
80
+
81
+ return () => clearInterval(interval);
82
+ }, [isConnected, connectionTime]);
83
+
84
+ // Update active subscriptions count
85
+ useEffect(() => {
86
+ if (!client || !isConnected) {
87
+ setActiveSubscriptions(0);
88
+ return;
89
+ }
90
+
91
+ const updateCount = () => {
92
+ try {
93
+ const centrifuge = client.getCentrifuge();
94
+ const subs = centrifuge.subscriptions();
95
+ setActiveSubscriptions(Object.keys(subs).length);
96
+ } catch (error) {
97
+ logger.error('Failed to get active subscriptions', error);
98
+ }
99
+ };
100
+
101
+ updateCount();
102
+ const interval = setInterval(updateCount, 2000);
103
+
104
+ return () => clearInterval(interval);
105
+ }, [client, isConnected]);
106
+
107
+ // Badge variant
108
+ if (variant === 'badge') {
109
+ return (
110
+ <Badge
111
+ variant={isConnected ? 'default' : 'destructive'}
112
+ className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''} ${className}`}
113
+ >
114
+ <span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
115
+ {isConnected ? 'Connected' : 'Disconnected'}
116
+ </Badge>
117
+ );
118
+ }
119
+
120
+ // Inline variant
121
+ if (variant === 'inline') {
122
+ return (
123
+ <div className={`flex items-center gap-2 ${className}`}>
124
+ {isConnected ? (
125
+ <Wifi className="h-4 w-4 text-green-600" />
126
+ ) : (
127
+ <WifiOff className="h-4 w-4 text-red-600" />
128
+ )}
129
+ <span className="text-sm font-medium">
130
+ {isConnected ? 'Connected' : 'Disconnected'}
131
+ </span>
132
+ {showUptime && uptime && (
133
+ <span className="text-xs text-muted-foreground">({uptime})</span>
134
+ )}
135
+ {showSubscriptions && (
136
+ <span className="text-xs text-muted-foreground flex items-center gap-1">
137
+ <Radio className="h-3 w-3" />
138
+ {activeSubscriptions}
139
+ </span>
140
+ )}
141
+ </div>
142
+ );
143
+ }
144
+
145
+ // Detailed variant
146
+ return (
147
+ <div className={`space-y-3 ${className}`}>
148
+ {/* Status Badge */}
149
+ <div className="flex items-center gap-2">
150
+ <Badge
151
+ variant={isConnected ? 'default' : 'destructive'}
152
+ className={`flex items-center gap-1 ${isConnected ? 'animate-pulse' : ''}`}
153
+ >
154
+ <span className={`h-2 w-2 rounded-full ${isConnected ? 'bg-green-500' : 'bg-red-500'}`} />
155
+ {isConnected ? 'Connected' : 'Disconnected'}
156
+ </Badge>
157
+ </div>
158
+
159
+ {/* Connection Info */}
160
+ {isConnected ? (
161
+ <div className="space-y-2">
162
+ {/* Uptime */}
163
+ {showUptime && uptime && (
164
+ <div className="flex items-center justify-between text-xs">
165
+ <span className="text-muted-foreground flex items-center gap-1">
166
+ <Clock className="h-3 w-3" />
167
+ Uptime:
168
+ </span>
169
+ <span className="font-mono font-medium">{uptime}</span>
170
+ </div>
171
+ )}
172
+
173
+ {/* Active Subscriptions */}
174
+ {showSubscriptions && (
175
+ <div className="flex items-center justify-between text-xs">
176
+ <span className="text-muted-foreground flex items-center gap-1">
177
+ <Radio className="h-3 w-3" />
178
+ Subscriptions:
179
+ </span>
180
+ <span className="font-mono font-medium">{activeSubscriptions}</span>
181
+ </div>
182
+ )}
183
+ </div>
184
+ ) : (
185
+ <div className="text-xs text-muted-foreground p-2 rounded bg-red-50 dark:bg-red-950/20">
186
+ Real-time features unavailable
187
+ </div>
188
+ )}
189
+ </div>
190
+ );
191
+ }
192
+
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Connection Status Card Component
3
+ *
4
+ * Card wrapper for ConnectionStatus - ready for dashboard widgets
5
+ */
6
+
7
+ 'use client';
8
+
9
+ import React from 'react';
10
+ import { Card, CardContent, CardHeader, CardTitle } from '@djangocfg/ui';
11
+ import { Wifi, WifiOff } from 'lucide-react';
12
+ import { useCentrifugo } from '../../providers/CentrifugoProvider';
13
+ import { ConnectionStatus } from './ConnectionStatus';
14
+
15
+ // ─────────────────────────────────────────────────────────────────────────
16
+ // Types
17
+ // ─────────────────────────────────────────────────────────────────────────
18
+
19
+ export interface ConnectionStatusCardProps {
20
+ showUptime?: boolean;
21
+ showSubscriptions?: boolean;
22
+ className?: string;
23
+ }
24
+
25
+ // ─────────────────────────────────────────────────────────────────────────
26
+ // Component
27
+ // ─────────────────────────────────────────────────────────────────────────
28
+
29
+ export function ConnectionStatusCard({
30
+ showUptime = true,
31
+ showSubscriptions = true,
32
+ className = '',
33
+ }: ConnectionStatusCardProps) {
34
+ const { isConnected } = useCentrifugo();
35
+
36
+ const statusColor = isConnected ? 'border-green-500' : 'border-red-500';
37
+
38
+ return (
39
+ <Card className={`${statusColor} ${className}`} style={{ borderLeftWidth: '4px' }}>
40
+ <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
41
+ <CardTitle className="text-sm font-medium">WebSocket Status</CardTitle>
42
+ <div className={isConnected ? 'text-green-600' : 'text-red-600'}>
43
+ {isConnected ? <Wifi className="h-4 w-4" /> : <WifiOff className="h-4 w-4" />}
44
+ </div>
45
+ </CardHeader>
46
+ <CardContent>
47
+ <ConnectionStatus
48
+ variant="detailed"
49
+ showUptime={showUptime}
50
+ showSubscriptions={showSubscriptions}
51
+ />
52
+ </CardContent>
53
+ </Card>
54
+ );
55
+ }
56
+
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Connection Status Components
3
+ */
4
+
5
+ export { ConnectionStatus } from './ConnectionStatus';
6
+ export { ConnectionStatusCard } from './ConnectionStatusCard';
7
+ export type { ConnectionStatusProps } from './ConnectionStatus';
8
+ export type { ConnectionStatusCardProps } from './ConnectionStatusCard';
9
+