claude-team-dashboard 1.2.2

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 claude-team-dashboard might be problematic. Click here for more details.

Files changed (49) hide show
  1. package/CHANGELOG.md +76 -0
  2. package/LICENSE +21 -0
  3. package/README.md +722 -0
  4. package/cleanup.js +73 -0
  5. package/config.js +50 -0
  6. package/dist/assets/icons-Ijf8rQIc.js +1 -0
  7. package/dist/assets/index-Cqc1m1x_.css +1 -0
  8. package/dist/assets/index-jGy3ms0W.js +9 -0
  9. package/dist/assets/react-vendor-DbmSkCAF.js +1 -0
  10. package/dist/index.html +16 -0
  11. package/index.html +13 -0
  12. package/package.json +93 -0
  13. package/server.js +953 -0
  14. package/src/App.jsx +372 -0
  15. package/src/animations-enhanced.css +929 -0
  16. package/src/animations.css +783 -0
  17. package/src/components/ActivityFeed.jsx +289 -0
  18. package/src/components/AgentActivity.jsx +104 -0
  19. package/src/components/AgentCard.jsx +163 -0
  20. package/src/components/AgentOutputViewer.jsx +334 -0
  21. package/src/components/ArchiveViewer.jsx +283 -0
  22. package/src/components/ConnectionStatus.jsx +124 -0
  23. package/src/components/DetailedTaskProgress.jsx +126 -0
  24. package/src/components/ErrorBoundary.jsx +132 -0
  25. package/src/components/Header.jsx +154 -0
  26. package/src/components/LiveAgentStream.jsx +176 -0
  27. package/src/components/LiveCommunication.jsx +326 -0
  28. package/src/components/LiveMetrics.jsx +100 -0
  29. package/src/components/RealTimeMessages.jsx +298 -0
  30. package/src/components/SkeletonLoader.jsx +384 -0
  31. package/src/components/StatsOverview.jsx +209 -0
  32. package/src/components/SystemStatus.jsx +57 -0
  33. package/src/components/TaskList.jsx +306 -0
  34. package/src/components/TeamCard.jsx +126 -0
  35. package/src/components/TeamHistory.jsx +204 -0
  36. package/src/components/__tests__/ConnectionStatus.test.jsx +54 -0
  37. package/src/components/__tests__/StatsOverview.test.jsx +66 -0
  38. package/src/config/constants.js +59 -0
  39. package/src/hooks/useCounterAnimation.js +219 -0
  40. package/src/hooks/useWebSocket.js +76 -0
  41. package/src/index.css +1818 -0
  42. package/src/main.jsx +17 -0
  43. package/src/polish-enhancements.css +303 -0
  44. package/src/premium-visual-polish.css +830 -0
  45. package/src/responsive-enhancements.css +666 -0
  46. package/src/styles/theme.css +395 -0
  47. package/src/test/setup.js +19 -0
  48. package/start.js +36 -0
  49. package/vite.config.js +37 -0
@@ -0,0 +1,126 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Users, ChevronDown, ChevronUp, Activity, Clock, CheckCircle, Loader } from 'lucide-react';
4
+ import { AgentCard } from './AgentCard';
5
+ import { TaskList } from './TaskList';
6
+ import dayjs from 'dayjs';
7
+ import relativeTime from 'dayjs/plugin/relativeTime';
8
+ dayjs.extend(relativeTime);
9
+
10
+ export function TeamCard({ team }) {
11
+ const [isExpanded, setIsExpanded] = useState(true);
12
+
13
+ const { name, config, tasks, lastUpdated } = team;
14
+ const members = config.members || [];
15
+ const lead = members.find(m => m.name === config.leadName);
16
+
17
+ const taskStats = {
18
+ pending: tasks.filter(t => t.status === 'pending').length,
19
+ inProgress: tasks.filter(t => t.status === 'in_progress').length,
20
+ completed: tasks.filter(t => t.status === 'completed').length
21
+ };
22
+
23
+ return (
24
+ <div className="card border-l-4 border-l-claude-orange">
25
+ <div className="flex items-center justify-between mb-4">
26
+ <div className="flex items-center gap-3">
27
+ <div className="bg-claude-orange/20 p-2 rounded-lg">
28
+ <Users className="h-6 w-6 text-claude-orange" />
29
+ </div>
30
+ <div>
31
+ <h3 className="text-xl font-bold text-white">{name}</h3>
32
+ {config.description && (
33
+ <p className="text-gray-400 text-sm mt-1">{config.description}</p>
34
+ )}
35
+ </div>
36
+ </div>
37
+ <button
38
+ onClick={() => setIsExpanded(!isExpanded)}
39
+ className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
40
+ aria-label={isExpanded ? "Collapse team details" : "Expand team details"}
41
+ aria-expanded={isExpanded}
42
+ >
43
+ {isExpanded ? (
44
+ <ChevronUp className="h-5 w-5 text-gray-400" />
45
+ ) : (
46
+ <ChevronDown className="h-5 w-5 text-gray-400" />
47
+ )}
48
+ </button>
49
+ </div>
50
+
51
+ <div className="flex flex-wrap gap-2 mb-4">
52
+ <div className="flex items-center gap-2 bg-gray-700/50 px-3 py-1 rounded-full">
53
+ <Users className="h-4 w-4 text-gray-400" />
54
+ <span className="text-sm text-gray-300">{members.length} agents</span>
55
+ </div>
56
+ <div className="flex items-center gap-2 bg-gray-700/50 px-3 py-1 rounded-full">
57
+ <Activity className="h-4 w-4 text-gray-400" />
58
+ <span className="text-sm text-gray-300">{tasks.length} tasks</span>
59
+ </div>
60
+ <div className="flex items-center gap-2 bg-gray-700/50 px-3 py-1 rounded-full">
61
+ <Clock className="h-4 w-4 text-gray-400" />
62
+ <span className="text-sm text-gray-300">
63
+ {dayjs(new Date(lastUpdated)).fromNow()}
64
+ </span>
65
+ </div>
66
+ </div>
67
+
68
+ <div className="flex gap-2 mb-4">
69
+ <span className="badge badge-pending">
70
+ <Clock className="h-3 w-3 inline-block mr-1" aria-hidden="true" />
71
+ {taskStats.pending} pending
72
+ </span>
73
+ <span className="badge badge-in-progress">
74
+ <Loader className="h-3 w-3 inline-block mr-1 animate-spin" aria-hidden="true" />
75
+ {taskStats.inProgress} in progress
76
+ </span>
77
+ <span className="badge badge-completed">
78
+ <CheckCircle className="h-3 w-3 inline-block mr-1" aria-hidden="true" />
79
+ {taskStats.completed} completed
80
+ </span>
81
+ </div>
82
+
83
+ {isExpanded && (
84
+ <div className="space-y-6 mt-6">
85
+ <div>
86
+ <h4 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
87
+ <Users className="h-5 w-5" />
88
+ Team Members
89
+ </h4>
90
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
91
+ {lead && (
92
+ <AgentCard agent={lead} isLead={true} />
93
+ )}
94
+ {members
95
+ .filter(m => m.name !== config.leadName)
96
+ .map((agent, index) => (
97
+ <AgentCard key={index} agent={agent} isLead={false} />
98
+ ))}
99
+ </div>
100
+ </div>
101
+
102
+ <div>
103
+ <h4 className="text-lg font-semibold text-white mb-3 flex items-center gap-2">
104
+ <Activity className="h-5 w-5" />
105
+ Tasks
106
+ </h4>
107
+ <TaskList tasks={tasks} />
108
+ </div>
109
+ </div>
110
+ )}
111
+ </div>
112
+ );
113
+ }
114
+
115
+ TeamCard.propTypes = {
116
+ team: PropTypes.shape({
117
+ name: PropTypes.string.isRequired,
118
+ config: PropTypes.shape({
119
+ description: PropTypes.string,
120
+ leadName: PropTypes.string,
121
+ members: PropTypes.arrayOf(PropTypes.object)
122
+ }),
123
+ tasks: PropTypes.array,
124
+ lastUpdated: PropTypes.string
125
+ }).isRequired
126
+ };
@@ -0,0 +1,204 @@
1
+ import React, { useState } from 'react';
2
+ import { History, Users, CheckCircle2, Clock, ChevronDown, ChevronRight } from 'lucide-react';
3
+ import dayjs from 'dayjs';
4
+ import relativeTime from 'dayjs/plugin/relativeTime';
5
+ dayjs.extend(relativeTime);
6
+
7
+ export function TeamHistory({ teamHistory }) {
8
+ const [expandedTeam, setExpandedTeam] = useState(null);
9
+
10
+ if (!teamHistory || teamHistory.length === 0) {
11
+ return (
12
+ <div className="card">
13
+ <div className="flex items-center gap-2 mb-4">
14
+ <History className="h-5 w-5 text-claude-orange" />
15
+ <h3 className="text-lg font-semibold text-white">Team History</h3>
16
+ </div>
17
+ <div className="text-center py-12 text-gray-400">
18
+ <History className="h-16 w-16 mx-auto mb-3 opacity-50" />
19
+ <p className="text-sm">No team history yet</p>
20
+ <p className="text-xs mt-1">Past teams will appear here</p>
21
+ </div>
22
+ </div>
23
+ );
24
+ }
25
+
26
+ const toggleTeam = (teamName) => {
27
+ setExpandedTeam(expandedTeam === teamName ? null : teamName);
28
+ };
29
+
30
+ const getTaskStats = (tasks) => {
31
+ return {
32
+ total: tasks.length,
33
+ completed: tasks.filter(t => t.status === 'completed').length,
34
+ inProgress: tasks.filter(t => t.status === 'in_progress').length,
35
+ pending: tasks.filter(t => t.status === 'pending').length
36
+ };
37
+ };
38
+
39
+ return (
40
+ <div className="card">
41
+ <div className="flex items-center justify-between mb-4">
42
+ <div className="flex items-center gap-2">
43
+ <History className="h-5 w-5 text-claude-orange" />
44
+ <h3 className="text-lg font-semibold text-white">Team History</h3>
45
+ </div>
46
+ <span className="text-sm text-gray-400">
47
+ {teamHistory.length} team{teamHistory.length !== 1 ? 's' : ''}
48
+ </span>
49
+ </div>
50
+
51
+ <div className="space-y-3 max-h-[600px] overflow-y-auto">
52
+ {teamHistory.map((team, index) => {
53
+ const stats = getTaskStats(team.tasks);
54
+ const isExpanded = expandedTeam === team.name;
55
+ const completionRate = stats.total > 0 ? (stats.completed / stats.total) * 100 : 0;
56
+
57
+ return (
58
+ <div
59
+ key={team.name}
60
+ className="border border-gray-700 rounded-xl overflow-hidden transition-all hover:border-claude-orange/50"
61
+ style={{
62
+ animation: `fadeInScale 0.3s ease-out ${index * 0.05}s backwards`
63
+ }}
64
+ >
65
+ {/* Team Header */}
66
+ <div
67
+ className="p-4 bg-gray-700/30 cursor-pointer hover:bg-gray-700/50 transition-colors"
68
+ onClick={() => toggleTeam(team.name)}
69
+ role="button"
70
+ tabIndex={0}
71
+ aria-expanded={isExpanded}
72
+ aria-label={isExpanded ? `Collapse ${team.name} details` : `Expand ${team.name} details`}
73
+ onKeyDown={(e) => {
74
+ if (e.key === 'Enter' || e.key === ' ') {
75
+ e.preventDefault();
76
+ toggleTeam(team.name);
77
+ }
78
+ }}
79
+ >
80
+ <div className="flex items-start gap-3">
81
+ <div className="mt-1" aria-hidden="true">
82
+ {isExpanded ? (
83
+ <ChevronDown className="h-5 w-5 text-gray-400" />
84
+ ) : (
85
+ <ChevronRight className="h-5 w-5 text-gray-400" />
86
+ )}
87
+ </div>
88
+
89
+ <div className="flex-1 min-w-0">
90
+ <div className="flex items-center gap-2 mb-1">
91
+ <h4 className="text-white font-semibold">{team.name}</h4>
92
+ {team.isActive && (
93
+ <span className="px-2 py-0.5 bg-green-500/20 text-green-400 text-xs rounded-full border border-green-500/30">
94
+ Active
95
+ </span>
96
+ )}
97
+ </div>
98
+
99
+ <div className="flex items-center gap-4 text-xs text-gray-400 mb-2">
100
+ <div className="flex items-center gap-1">
101
+ <Users className="h-3 w-3" />
102
+ {team.config.members?.length || 0} members
103
+ </div>
104
+ <div className="flex items-center gap-1">
105
+ <CheckCircle2 className="h-3 w-3" />
106
+ {stats.total} tasks
107
+ </div>
108
+ <div className="flex items-center gap-1">
109
+ <Clock className="h-3 w-3" />
110
+ {dayjs(team.lastModified).fromNow()}
111
+ </div>
112
+ </div>
113
+
114
+ {/* Progress Bar */}
115
+ <div className="w-full bg-gray-600 rounded-full h-2">
116
+ <div
117
+ className="bg-gradient-to-r from-green-500 to-emerald-400 h-2 rounded-full transition-all duration-500"
118
+ style={{ width: `${completionRate}%` }}
119
+ ></div>
120
+ </div>
121
+ <div className="mt-1 text-xs text-gray-400">
122
+ {stats.completed}/{stats.total} tasks completed ({Math.round(completionRate)}%)
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </div>
127
+
128
+ {/* Expanded Content */}
129
+ {isExpanded && (
130
+ <div className="p-4 bg-gray-800/30 border-t border-gray-700 space-y-4">
131
+ {/* Members */}
132
+ <div>
133
+ <h5 className="text-sm font-semibold text-white mb-2">Team Members</h5>
134
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2">
135
+ {team.config.members?.map((member, idx) => (
136
+ <div
137
+ key={idx}
138
+ className="p-2 rounded-lg bg-gray-700/30 border border-gray-600/50"
139
+ >
140
+ <div className="font-medium text-sm text-white">{member.name}</div>
141
+ <div className="text-xs text-gray-400">{member.agentType}</div>
142
+ </div>
143
+ ))}
144
+ </div>
145
+ </div>
146
+
147
+ {/* Task Stats */}
148
+ <div>
149
+ <h5 className="text-sm font-semibold text-white mb-2">Task Summary</h5>
150
+ <div className="grid grid-cols-4 gap-2">
151
+ <div className="p-2 rounded-lg bg-gray-700/30 text-center">
152
+ <div className="text-lg font-bold text-white">{stats.total}</div>
153
+ <div className="text-xs text-gray-400">Total</div>
154
+ </div>
155
+ <div className="p-2 rounded-lg bg-green-500/10 text-center">
156
+ <div className="text-lg font-bold text-green-400">{stats.completed}</div>
157
+ <div className="text-xs text-gray-400">Done</div>
158
+ </div>
159
+ <div className="p-2 rounded-lg bg-blue-500/10 text-center">
160
+ <div className="text-lg font-bold text-blue-400">{stats.inProgress}</div>
161
+ <div className="text-xs text-gray-400">Active</div>
162
+ </div>
163
+ <div className="p-2 rounded-lg bg-yellow-500/10 text-center">
164
+ <div className="text-lg font-bold text-yellow-400">{stats.pending}</div>
165
+ <div className="text-xs text-gray-400">Pending</div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ {/* Recent Tasks */}
171
+ {team.tasks.slice(0, 5).length > 0 && (
172
+ <div>
173
+ <h5 className="text-sm font-semibold text-white mb-2">Recent Tasks</h5>
174
+ <div className="space-y-1">
175
+ {team.tasks.slice(0, 5).map((task, idx) => (
176
+ <div
177
+ key={idx}
178
+ className="p-2 rounded-lg bg-gray-700/20 text-sm"
179
+ >
180
+ <div className="flex items-center gap-2">
181
+ <span className={`h-2 w-2 rounded-full flex-shrink-0 ${
182
+ task.status === 'completed' ? 'bg-green-400' :
183
+ task.status === 'in_progress' ? 'bg-blue-400' :
184
+ 'bg-yellow-400'
185
+ }`}></span>
186
+ <span className="text-white truncate">{task.subject}</span>
187
+ {task.owner && (
188
+ <span className="ml-auto text-xs text-gray-400">{task.owner}</span>
189
+ )}
190
+ </div>
191
+ </div>
192
+ ))}
193
+ </div>
194
+ </div>
195
+ )}
196
+ </div>
197
+ )}
198
+ </div>
199
+ );
200
+ })}
201
+ </div>
202
+ </div>
203
+ );
204
+ }
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { ConnectionStatus } from '../ConnectionStatus';
4
+
5
+ describe('ConnectionStatus Component', () => {
6
+ it('renders connected status correctly', () => {
7
+ render(<ConnectionStatus isConnected={true} error={null} />);
8
+
9
+ const statusText = screen.getByText(/connected/i);
10
+ expect(statusText).toBeInTheDocument();
11
+ });
12
+
13
+ it('renders connecting status when disconnected without error', () => {
14
+ render(<ConnectionStatus isConnected={false} error={null} />);
15
+
16
+ const statusText = screen.getByText(/connecting/i);
17
+ expect(statusText).toBeInTheDocument();
18
+ });
19
+
20
+ it('renders error message when error provided', () => {
21
+ const errorMessage = 'Connection failed';
22
+ render(<ConnectionStatus isConnected={false} error={errorMessage} />);
23
+
24
+ const errorText = screen.getByText(errorMessage);
25
+ expect(errorText).toBeInTheDocument();
26
+ });
27
+
28
+ it('displays green indicator when connected', () => {
29
+ const { container } = render(<ConnectionStatus isConnected={true} error={null} />);
30
+
31
+ // Check for green color classes
32
+ const statusDiv = container.querySelector('.bg-green-500\\/20');
33
+ expect(statusDiv).toBeInTheDocument();
34
+ expect(statusDiv).toHaveClass('text-green-400');
35
+ });
36
+
37
+ it('displays red indicator when error occurs', () => {
38
+ const { container } = render(<ConnectionStatus isConnected={false} error="Error" />);
39
+
40
+ // Check for red color classes
41
+ const statusDiv = container.querySelector('.bg-red-500\\/20');
42
+ expect(statusDiv).toBeInTheDocument();
43
+ expect(statusDiv).toHaveClass('text-red-400');
44
+ });
45
+
46
+ it('displays yellow indicator when connecting', () => {
47
+ const { container } = render(<ConnectionStatus isConnected={false} error={null} />);
48
+
49
+ // Check for yellow color classes
50
+ const statusDiv = container.querySelector('.bg-yellow-500\\/20');
51
+ expect(statusDiv).toBeInTheDocument();
52
+ expect(statusDiv).toHaveClass('text-yellow-400');
53
+ });
54
+ });
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { StatsOverview } from '../StatsOverview';
4
+
5
+ describe('StatsOverview Component', () => {
6
+ const mockStats = {
7
+ totalTeams: 4,
8
+ totalAgents: 19,
9
+ totalTasks: 42,
10
+ inProgressTasks: 18,
11
+ completedTasks: 16,
12
+ blockedTasks: 2
13
+ };
14
+
15
+ it('renders all stats correctly', () => {
16
+ render(<StatsOverview stats={mockStats} />);
17
+
18
+ expect(screen.getByText('4')).toBeInTheDocument();
19
+ expect(screen.getByText('19')).toBeInTheDocument();
20
+ expect(screen.getByText('42')).toBeInTheDocument();
21
+ expect(screen.getByText('18')).toBeInTheDocument();
22
+ expect(screen.getByText('16')).toBeInTheDocument();
23
+ expect(screen.getByText('2')).toBeInTheDocument();
24
+ });
25
+
26
+ it('renders nothing when no stats provided', () => {
27
+ const { container } = render(<StatsOverview stats={null} />);
28
+
29
+ // Component returns null when stats is null
30
+ expect(container.firstChild).toBeNull();
31
+ });
32
+
33
+ it('renders all stat labels correctly', () => {
34
+ render(<StatsOverview stats={mockStats} />);
35
+
36
+ expect(screen.getByText('Active Teams')).toBeInTheDocument();
37
+ expect(screen.getByText('Total Agents')).toBeInTheDocument();
38
+ expect(screen.getByText('Total Tasks')).toBeInTheDocument();
39
+ expect(screen.getByText('In Progress')).toBeInTheDocument();
40
+ expect(screen.getByText('Completed')).toBeInTheDocument();
41
+ expect(screen.getByText('Blocked')).toBeInTheDocument();
42
+ });
43
+
44
+ it('renders with correct grid layout', () => {
45
+ const { container } = render(<StatsOverview stats={mockStats} />);
46
+
47
+ const grid = container.querySelector('.grid');
48
+ expect(grid).toHaveClass('grid-cols-2', 'md:grid-cols-3', 'lg:grid-cols-6');
49
+ });
50
+
51
+ it('renders icons for each stat', () => {
52
+ const { container } = render(<StatsOverview stats={mockStats} />);
53
+
54
+ // Check for 6 icon containers (one for each stat)
55
+ const iconContainers = container.querySelectorAll('.flex-shrink-0');
56
+ expect(iconContainers.length).toBe(6);
57
+ });
58
+
59
+ it('displays stat values with correct formatting', () => {
60
+ render(<StatsOverview stats={mockStats} />);
61
+
62
+ // Check that values are displayed as bold white text
63
+ const valueElements = screen.getByText('4').closest('p');
64
+ expect(valueElements).toHaveClass('text-xl', 'font-bold', 'text-white');
65
+ });
66
+ });
@@ -0,0 +1,59 @@
1
+ // WebSocket Configuration
2
+ export const WS_CONFIG = {
3
+ RECONNECT_DELAY: 3000,
4
+ PING_INTERVAL: 30000,
5
+ MAX_RECONNECT_ATTEMPTS: 5
6
+ };
7
+
8
+ // Server Configuration
9
+ export const SERVER_CONFIG = {
10
+ WS_PORT: 3001,
11
+ API_PORT: 3001,
12
+ ALLOWED_ORIGINS: ['http://localhost:5173', 'http://127.0.0.1:5173']
13
+ };
14
+
15
+ // UI Configuration
16
+ export const UI_CONFIG = {
17
+ MAX_ACTIVITY_ITEMS: 50,
18
+ ACTIVITY_UPDATE_INTERVAL: 5000,
19
+ PULSE_ANIMATION_DURATION: 300,
20
+ DEFAULT_TEAM_EXPANDED: true
21
+ };
22
+
23
+ // Task Status
24
+ export const TASK_STATUS = {
25
+ PENDING: 'pending',
26
+ IN_PROGRESS: 'in_progress',
27
+ COMPLETED: 'completed',
28
+ DELETED: 'deleted'
29
+ };
30
+
31
+ // Theme Colors
32
+ export const THEME_COLORS = {
33
+ CLAUDE_ORANGE: '#f28234',
34
+ SUCCESS: '#10b981',
35
+ WARNING: '#f59e0b',
36
+ ERROR: '#ef4444',
37
+ INFO: '#3b82f6'
38
+ };
39
+
40
+ // Message Types
41
+ export const MESSAGE_TYPES = {
42
+ INITIAL_DATA: 'initial_data',
43
+ TEAMS_UPDATE: 'teams_update',
44
+ TASK_UPDATE: 'task_update'
45
+ };
46
+
47
+ // Rate Limiting
48
+ export const RATE_LIMIT = {
49
+ WINDOW_MS: 15 * 60 * 1000, // 15 minutes
50
+ MAX_REQUESTS: 100
51
+ };
52
+
53
+ // File Watching
54
+ export const WATCH_CONFIG = {
55
+ POLLING_INTERVAL: 1000,
56
+ STABILITY_THRESHOLD: 500,
57
+ POLL_INTERVAL: 100,
58
+ DEPTH: 10
59
+ };