@atercates/claude-deck 0.2.1

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.
Files changed (293) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +123 -0
  3. package/app/api/claude/hidden/route.ts +66 -0
  4. package/app/api/claude/projects/[name]/sessions/route.ts +71 -0
  5. package/app/api/claude/projects/route.ts +44 -0
  6. package/app/api/code-search/available/route.ts +12 -0
  7. package/app/api/code-search/route.ts +47 -0
  8. package/app/api/dev-servers/[id]/logs/route.ts +23 -0
  9. package/app/api/dev-servers/[id]/restart/route.ts +20 -0
  10. package/app/api/dev-servers/[id]/route.ts +51 -0
  11. package/app/api/dev-servers/[id]/stop/route.ts +20 -0
  12. package/app/api/dev-servers/detect/route.ts +39 -0
  13. package/app/api/dev-servers/route.ts +48 -0
  14. package/app/api/exec/route.ts +60 -0
  15. package/app/api/files/content/route.ts +76 -0
  16. package/app/api/files/route.ts +37 -0
  17. package/app/api/files/upload-temp/route.ts +41 -0
  18. package/app/api/git/check/route.ts +54 -0
  19. package/app/api/git/clone/route.ts +99 -0
  20. package/app/api/git/commit/route.ts +75 -0
  21. package/app/api/git/discard/route.ts +38 -0
  22. package/app/api/git/file-content/route.ts +64 -0
  23. package/app/api/git/history/[hash]/diff/route.ts +38 -0
  24. package/app/api/git/history/[hash]/route.ts +34 -0
  25. package/app/api/git/history/route.ts +27 -0
  26. package/app/api/git/multi-status/route.ts +46 -0
  27. package/app/api/git/pr/route.ts +164 -0
  28. package/app/api/git/push/route.ts +64 -0
  29. package/app/api/git/stage/route.ts +40 -0
  30. package/app/api/git/status/route.ts +51 -0
  31. package/app/api/git/unstage/route.ts +46 -0
  32. package/app/api/groups/[...path]/route.ts +136 -0
  33. package/app/api/groups/route.ts +93 -0
  34. package/app/api/orchestrate/spawn/route.ts +45 -0
  35. package/app/api/orchestrate/workers/[id]/route.ts +89 -0
  36. package/app/api/orchestrate/workers/route.ts +31 -0
  37. package/app/api/projects/[id]/detect/route.ts +27 -0
  38. package/app/api/projects/[id]/dev-servers/[dsId]/route.ts +66 -0
  39. package/app/api/projects/[id]/dev-servers/route.ts +51 -0
  40. package/app/api/projects/[id]/repositories/[repoId]/route.ts +67 -0
  41. package/app/api/projects/[id]/repositories/route.ts +74 -0
  42. package/app/api/projects/[id]/route.ts +108 -0
  43. package/app/api/projects/detect/route.ts +33 -0
  44. package/app/api/projects/route.ts +59 -0
  45. package/app/api/sessions/[id]/claude-session/route.ts +42 -0
  46. package/app/api/sessions/[id]/fork/route.ts +74 -0
  47. package/app/api/sessions/[id]/mcp-config/route.ts +34 -0
  48. package/app/api/sessions/[id]/messages/route.ts +60 -0
  49. package/app/api/sessions/[id]/pr/route.ts +188 -0
  50. package/app/api/sessions/[id]/preview/route.ts +42 -0
  51. package/app/api/sessions/[id]/route.ts +229 -0
  52. package/app/api/sessions/[id]/send-keys/route.ts +119 -0
  53. package/app/api/sessions/[id]/summarize/route.ts +331 -0
  54. package/app/api/sessions/init-script/route.ts +84 -0
  55. package/app/api/sessions/route.ts +209 -0
  56. package/app/api/sessions/status/route.ts +237 -0
  57. package/app/api/system/route.ts +9 -0
  58. package/app/api/tmux/kill-all/route.ts +57 -0
  59. package/app/api/tmux/rename/route.ts +30 -0
  60. package/app/globals.css +174 -0
  61. package/app/icon.svg +11 -0
  62. package/app/layout.tsx +122 -0
  63. package/app/page.tsx +629 -0
  64. package/components/ChatMessage.tsx +65 -0
  65. package/components/ChatView.tsx +276 -0
  66. package/components/ClaudeProjects/ClaudeProjectCard.tsx +195 -0
  67. package/components/ClaudeProjects/ClaudeProjectsSection.tsx +89 -0
  68. package/components/ClaudeProjects/ClaudeSessionCard.tsx +100 -0
  69. package/components/ClaudeProjects/index.ts +1 -0
  70. package/components/CodeSearch/CodeSearchResults.tsx +177 -0
  71. package/components/ConductorPanel.tsx +256 -0
  72. package/components/DevServers/DevServerCard.tsx +311 -0
  73. package/components/DevServers/DevServersSection.tsx +91 -0
  74. package/components/DevServers/ServerLogsModal.tsx +151 -0
  75. package/components/DevServers/StartServerDialog.tsx +359 -0
  76. package/components/DevServers/index.ts +4 -0
  77. package/components/DiffViewer/DiffModal.tsx +151 -0
  78. package/components/DiffViewer/UnifiedDiff.tsx +185 -0
  79. package/components/DiffViewer/index.tsx +2 -0
  80. package/components/DirectoryPicker.tsx +355 -0
  81. package/components/FileExplorer/FileEditor.tsx +276 -0
  82. package/components/FileExplorer/FileTabs.tsx +118 -0
  83. package/components/FileExplorer/FileTree.tsx +214 -0
  84. package/components/FileExplorer/HtmlRenderer.tsx +16 -0
  85. package/components/FileExplorer/MarkdownRenderer.tsx +18 -0
  86. package/components/FileExplorer/index.tsx +520 -0
  87. package/components/FilePicker.tsx +339 -0
  88. package/components/FolderPicker.tsx +201 -0
  89. package/components/GitDrawer/FileEditDialog.tsx +400 -0
  90. package/components/GitDrawer/index.tsx +464 -0
  91. package/components/GitPanel/CommitForm.tsx +205 -0
  92. package/components/GitPanel/CommitHistory.tsx +174 -0
  93. package/components/GitPanel/CommitItem.tsx +196 -0
  94. package/components/GitPanel/FileChanges.tsx +414 -0
  95. package/components/GitPanel/GitPanelTabs.tsx +39 -0
  96. package/components/GitPanel/index.tsx +817 -0
  97. package/components/MessageInput.tsx +82 -0
  98. package/components/NewClaudeSessionDialog.tsx +166 -0
  99. package/components/NewSessionDialog/AdvancedSettings.tsx +78 -0
  100. package/components/NewSessionDialog/AgentSelector.tsx +37 -0
  101. package/components/NewSessionDialog/CreatingOverlay.tsx +94 -0
  102. package/components/NewSessionDialog/NewSessionDialog.types.ts +136 -0
  103. package/components/NewSessionDialog/ProjectSelector.tsx +146 -0
  104. package/components/NewSessionDialog/WorkingDirectoryInput.tsx +55 -0
  105. package/components/NewSessionDialog/WorktreeSection.tsx +92 -0
  106. package/components/NewSessionDialog/hooks/useNewSessionForm.ts +370 -0
  107. package/components/NewSessionDialog/index.tsx +106 -0
  108. package/components/NotificationSettings.tsx +127 -0
  109. package/components/PRCreationModal.tsx +272 -0
  110. package/components/Pane/DesktopTabBar.tsx +353 -0
  111. package/components/Pane/MobileTabBar.tsx +210 -0
  112. package/components/Pane/OpenInVSCode.tsx +69 -0
  113. package/components/Pane/PaneSkeletons.tsx +57 -0
  114. package/components/Pane/index.tsx +558 -0
  115. package/components/PaneLayout.tsx +60 -0
  116. package/components/Projects/DevServersSection.tsx +140 -0
  117. package/components/Projects/DirectoryField.tsx +92 -0
  118. package/components/Projects/NewProjectDialog.tsx +188 -0
  119. package/components/Projects/NewProjectDialog.types.ts +46 -0
  120. package/components/Projects/ProjectCard.tsx +276 -0
  121. package/components/Projects/ProjectSettingsDialog.tsx +811 -0
  122. package/components/Projects/hooks/useNewProjectForm.ts +249 -0
  123. package/components/Projects/index.ts +3 -0
  124. package/components/Providers.tsx +49 -0
  125. package/components/QuickSwitcher.tsx +306 -0
  126. package/components/SessionList/KillAllConfirm.tsx +46 -0
  127. package/components/SessionList/SelectionToolbar.tsx +164 -0
  128. package/components/SessionList/SessionList.types.ts +37 -0
  129. package/components/SessionList/SessionListHeader.tsx +71 -0
  130. package/components/SessionList/hooks/useSessionListMutations.ts +269 -0
  131. package/components/SessionList/index.tsx +189 -0
  132. package/components/ShellDrawer/index.tsx +106 -0
  133. package/components/SidebarFooter.tsx +55 -0
  134. package/components/Terminal/KeybarToggleButton.tsx +45 -0
  135. package/components/Terminal/ScrollToBottomButton.tsx +32 -0
  136. package/components/Terminal/SearchBar.tsx +71 -0
  137. package/components/Terminal/TerminalToolbar.tsx +551 -0
  138. package/components/Terminal/VirtualKeyboard.tsx +711 -0
  139. package/components/Terminal/constants.ts +20 -0
  140. package/components/Terminal/hooks/index.ts +5 -0
  141. package/components/Terminal/hooks/resize-handlers.ts +140 -0
  142. package/components/Terminal/hooks/terminal-init.ts +151 -0
  143. package/components/Terminal/hooks/touch-scroll.ts +155 -0
  144. package/components/Terminal/hooks/useTerminalConnection.ts +282 -0
  145. package/components/Terminal/hooks/useTerminalConnection.types.ts +39 -0
  146. package/components/Terminal/hooks/useTerminalSearch.ts +103 -0
  147. package/components/Terminal/hooks/websocket-connection.ts +274 -0
  148. package/components/Terminal/index.tsx +320 -0
  149. package/components/ThemeToggle.tsx +168 -0
  150. package/components/TmuxSessions.tsx +132 -0
  151. package/components/ToolCallDisplay.tsx +71 -0
  152. package/components/WorkerCard.tsx +245 -0
  153. package/components/a/ABadge.tsx +115 -0
  154. package/components/a/AButton.tsx +163 -0
  155. package/components/a/ADialog.tsx +93 -0
  156. package/components/a/ADropdownMenu.tsx +279 -0
  157. package/components/a/AIconButton.tsx +190 -0
  158. package/components/a/ASheet.tsx +150 -0
  159. package/components/a/ATooltip.tsx +77 -0
  160. package/components/a/index.ts +64 -0
  161. package/components/mobile/SwipeSidebar.tsx +122 -0
  162. package/components/ui/badge.tsx +41 -0
  163. package/components/ui/button.tsx +60 -0
  164. package/components/ui/context-menu.tsx +197 -0
  165. package/components/ui/dialog.tsx +143 -0
  166. package/components/ui/dropdown-menu.tsx +257 -0
  167. package/components/ui/input.tsx +21 -0
  168. package/components/ui/scroll-area.tsx +52 -0
  169. package/components/ui/select.tsx +159 -0
  170. package/components/ui/skeleton.tsx +111 -0
  171. package/components/ui/switch.tsx +31 -0
  172. package/components/ui/textarea.tsx +21 -0
  173. package/components/ui/tooltip.tsx +32 -0
  174. package/components/views/DesktopView.tsx +244 -0
  175. package/components/views/MobileView.tsx +110 -0
  176. package/components/views/types.ts +75 -0
  177. package/contexts/PaneContext.tsx +336 -0
  178. package/data/claude/index.ts +9 -0
  179. package/data/claude/keys.ts +6 -0
  180. package/data/claude/queries.ts +120 -0
  181. package/data/claude/useClaudeUpdates.ts +37 -0
  182. package/data/code-search/index.ts +2 -0
  183. package/data/code-search/keys.ts +7 -0
  184. package/data/code-search/queries.ts +61 -0
  185. package/data/dev-servers/index.ts +8 -0
  186. package/data/dev-servers/keys.ts +4 -0
  187. package/data/dev-servers/queries.ts +104 -0
  188. package/data/files/index.ts +3 -0
  189. package/data/files/keys.ts +4 -0
  190. package/data/files/queries.ts +25 -0
  191. package/data/git/keys.ts +15 -0
  192. package/data/git/queries.ts +395 -0
  193. package/data/groups/index.ts +1 -0
  194. package/data/groups/mutations.ts +95 -0
  195. package/data/projects/index.ts +10 -0
  196. package/data/projects/keys.ts +4 -0
  197. package/data/projects/queries.ts +193 -0
  198. package/data/repositories/index.ts +7 -0
  199. package/data/repositories/keys.ts +5 -0
  200. package/data/repositories/queries.ts +122 -0
  201. package/data/sessions/index.ts +12 -0
  202. package/data/sessions/keys.ts +8 -0
  203. package/data/sessions/queries.ts +218 -0
  204. package/data/statuses/index.ts +1 -0
  205. package/data/statuses/queries.ts +69 -0
  206. package/hooks/useCopyToClipboard.ts +48 -0
  207. package/hooks/useDevServersManager.ts +73 -0
  208. package/hooks/useDirectoryBrowser.ts +90 -0
  209. package/hooks/useDrawerAnimation.ts +27 -0
  210. package/hooks/useFileDrop.ts +87 -0
  211. package/hooks/useFileEditor.ts +184 -0
  212. package/hooks/useGroups.ts +37 -0
  213. package/hooks/useHomePath.ts +34 -0
  214. package/hooks/useKeyRepeat.ts +55 -0
  215. package/hooks/useKeybarVisibility.ts +42 -0
  216. package/hooks/useNotifications.ts +257 -0
  217. package/hooks/useProjects.ts +53 -0
  218. package/hooks/useSessionStatuses.ts +30 -0
  219. package/hooks/useSessions.ts +86 -0
  220. package/hooks/useSpeechRecognition.ts +124 -0
  221. package/hooks/useViewport.ts +32 -0
  222. package/hooks/useViewportHeight.ts +50 -0
  223. package/lib/async-operations.ts +35 -0
  224. package/lib/banner.ts +81 -0
  225. package/lib/claude/jsonl-cache.ts +86 -0
  226. package/lib/claude/jsonl-reader.ts +271 -0
  227. package/lib/claude/process-manager.ts +278 -0
  228. package/lib/claude/stream-parser.ts +173 -0
  229. package/lib/claude/types.ts +154 -0
  230. package/lib/claude/watcher.ts +71 -0
  231. package/lib/client/session-registry.ts +111 -0
  232. package/lib/code-search.ts +121 -0
  233. package/lib/db/index.ts +48 -0
  234. package/lib/db/migrations.ts +45 -0
  235. package/lib/db/queries.ts +460 -0
  236. package/lib/db/schema.ts +114 -0
  237. package/lib/db/types.ts +92 -0
  238. package/lib/db.ts +2 -0
  239. package/lib/dev-servers.ts +509 -0
  240. package/lib/diff-parser.ts +221 -0
  241. package/lib/env-setup.ts +285 -0
  242. package/lib/file-upload.ts +34 -0
  243. package/lib/file-utils.ts +50 -0
  244. package/lib/files.ts +207 -0
  245. package/lib/git-history.ts +294 -0
  246. package/lib/git-status.ts +391 -0
  247. package/lib/git.ts +257 -0
  248. package/lib/mcp-config.ts +81 -0
  249. package/lib/multi-repo-git.ts +179 -0
  250. package/lib/notifications.ts +219 -0
  251. package/lib/orchestration.ts +448 -0
  252. package/lib/panes.ts +232 -0
  253. package/lib/ports.ts +97 -0
  254. package/lib/pr-generation.ts +307 -0
  255. package/lib/pr.ts +234 -0
  256. package/lib/projects.ts +578 -0
  257. package/lib/providers/registry.ts +70 -0
  258. package/lib/providers.ts +121 -0
  259. package/lib/query-client.ts +14 -0
  260. package/lib/rangeSelectionUtils.ts +65 -0
  261. package/lib/status-detector.ts +375 -0
  262. package/lib/terminal-themes.ts +265 -0
  263. package/lib/theme-config.ts +327 -0
  264. package/lib/utils.ts +6 -0
  265. package/lib/worktrees.ts +262 -0
  266. package/mcp/orchestration-server.ts +438 -0
  267. package/package.json +139 -0
  268. package/postcss.config.mjs +7 -0
  269. package/public/icon.svg +10 -0
  270. package/public/icons/icon-128x128.png +0 -0
  271. package/public/icons/icon-144x144.png +0 -0
  272. package/public/icons/icon-152x152.png +0 -0
  273. package/public/icons/icon-192x192.png +0 -0
  274. package/public/icons/icon-384x384.png +0 -0
  275. package/public/icons/icon-512x512.png +0 -0
  276. package/public/icons/icon-72x72.png +0 -0
  277. package/public/icons/icon-96x96.png +0 -0
  278. package/public/manifest.json +61 -0
  279. package/public/sw.js +64 -0
  280. package/scripts/agent-os +91 -0
  281. package/scripts/install.sh +48 -0
  282. package/scripts/lib/ai-clis.sh +132 -0
  283. package/scripts/lib/commands.sh +487 -0
  284. package/scripts/lib/common.sh +89 -0
  285. package/scripts/lib/prerequisites.sh +462 -0
  286. package/scripts/setup.sh +134 -0
  287. package/server.ts +155 -0
  288. package/stores/fileOpen.ts +26 -0
  289. package/stores/index.ts +1 -0
  290. package/stores/initialPrompt.ts +24 -0
  291. package/stores/sessionSelection.ts +48 -0
  292. package/styles/themes.css +603 -0
  293. package/tsconfig.json +33 -0
@@ -0,0 +1,311 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import {
5
+ Play,
6
+ Square,
7
+ RefreshCw,
8
+ FileText,
9
+ Trash2,
10
+ Copy,
11
+ Check,
12
+ Server,
13
+ Container,
14
+ X,
15
+ ExternalLink,
16
+ } from "lucide-react";
17
+ import { cn } from "@/lib/utils";
18
+ import { useCopyToClipboard } from "@/hooks/useCopyToClipboard";
19
+ import type { DevServer, DevServerStatus } from "@/lib/db";
20
+
21
+ interface DevServerCardProps {
22
+ server: DevServer;
23
+ projectName?: string;
24
+ onStart: (id: string) => Promise<void>;
25
+ onStop: (id: string) => Promise<void>;
26
+ onRestart: (id: string) => Promise<void>;
27
+ onRemove: (id: string) => Promise<void>;
28
+ onViewLogs: (id: string) => void;
29
+ }
30
+
31
+ const statusConfig: Record<
32
+ DevServerStatus,
33
+ { color: string; bgColor: string; label: string }
34
+ > = {
35
+ running: {
36
+ color: "bg-green-500",
37
+ bgColor: "bg-green-500/10",
38
+ label: "Running",
39
+ },
40
+ stopped: {
41
+ color: "bg-zinc-500",
42
+ bgColor: "bg-zinc-500/10",
43
+ label: "Stopped",
44
+ },
45
+ starting: {
46
+ color: "bg-yellow-500",
47
+ bgColor: "bg-yellow-500/10",
48
+ label: "Starting",
49
+ },
50
+ failed: { color: "bg-red-500", bgColor: "bg-red-500/10", label: "Failed" },
51
+ };
52
+
53
+ export function DevServerCard({
54
+ server,
55
+ projectName,
56
+ onStart,
57
+ onStop,
58
+ onRestart,
59
+ onRemove,
60
+ onViewLogs,
61
+ }: DevServerCardProps) {
62
+ const [loading, setLoading] = useState(false);
63
+ const [confirmingStop, setConfirmingStop] = useState(false);
64
+ const { copied, copy } = useCopyToClipboard();
65
+
66
+ const status = statusConfig[server.status] || statusConfig.stopped;
67
+ const ports: number[] = JSON.parse(server.ports || "[]");
68
+ const primaryPort = ports[0];
69
+ const isRunning = server.status === "running";
70
+ const isStopped = server.status === "stopped";
71
+ const isFailed = server.status === "failed";
72
+
73
+ const handleAction = async (action: () => Promise<void>) => {
74
+ setLoading(true);
75
+ try {
76
+ await action();
77
+ } finally {
78
+ setLoading(false);
79
+ }
80
+ };
81
+
82
+ return (
83
+ <div
84
+ className={cn(
85
+ "border-border/50 rounded-md border px-2 py-1.5",
86
+ "bg-card/50 hover:bg-card/80 transition-colors"
87
+ )}
88
+ >
89
+ {/* Header row */}
90
+ <div className="flex items-center gap-2">
91
+ {/* Status dot */}
92
+ <div className={cn("h-2 w-2 rounded-full", status.color)} />
93
+
94
+ {/* Name */}
95
+ <span className="flex-1 truncate text-sm font-medium">
96
+ {server.name}
97
+ </span>
98
+
99
+ {/* Type badge */}
100
+ <span
101
+ className={cn(
102
+ "flex items-center gap-1 rounded px-1.5 py-0.5",
103
+ "text-muted-foreground text-[10px] font-medium",
104
+ "bg-muted/50"
105
+ )}
106
+ >
107
+ {server.type === "docker" ? (
108
+ <Container className="h-3 w-3" />
109
+ ) : (
110
+ <Server className="h-3 w-3" />
111
+ )}
112
+ {server.type === "docker" ? "Docker" : "Node"}
113
+ </span>
114
+ </div>
115
+
116
+ {/* Project name (if provided) */}
117
+ {projectName && (
118
+ <div className="text-muted-foreground mt-1 truncate text-xs">
119
+ {projectName}
120
+ </div>
121
+ )}
122
+
123
+ {/* Port badge */}
124
+ {primaryPort && (
125
+ <div className="mt-1 flex items-center gap-1">
126
+ {isRunning ? (
127
+ <a
128
+ href={`http://localhost:${primaryPort}`}
129
+ target="_blank"
130
+ rel="noopener noreferrer"
131
+ className={cn(
132
+ "flex items-center gap-1 rounded px-2 py-1",
133
+ "font-mono text-xs transition-colors",
134
+ "bg-primary/10 text-primary hover:bg-primary/20"
135
+ )}
136
+ >
137
+ <ExternalLink className="h-3 w-3" />
138
+ localhost:{primaryPort}
139
+ </a>
140
+ ) : (
141
+ <span
142
+ className={cn(
143
+ "flex items-center gap-1 rounded px-2 py-1",
144
+ "font-mono text-xs",
145
+ "bg-muted/30 text-muted-foreground"
146
+ )}
147
+ >
148
+ localhost:{primaryPort}
149
+ </span>
150
+ )}
151
+ <button
152
+ onClick={() => primaryPort && copy(`localhost:${primaryPort}`)}
153
+ disabled={!isRunning}
154
+ className={cn(
155
+ "flex items-center justify-center rounded p-1",
156
+ "text-xs transition-colors",
157
+ isRunning
158
+ ? "text-muted-foreground hover:text-foreground hover:bg-muted"
159
+ : "text-muted-foreground/50 cursor-not-allowed"
160
+ )}
161
+ title="Copy URL"
162
+ >
163
+ {copied ? (
164
+ <Check className="h-3 w-3 text-green-500" />
165
+ ) : (
166
+ <Copy className="h-3 w-3" />
167
+ )}
168
+ </button>
169
+ </div>
170
+ )}
171
+
172
+ {/* Actions */}
173
+ <div className="mt-1.5 flex items-center gap-1">
174
+ {isRunning && (
175
+ <>
176
+ {confirmingStop ? (
177
+ <>
178
+ <ActionButton
179
+ icon={Square}
180
+ label="Confirm"
181
+ onClick={() => {
182
+ setConfirmingStop(false);
183
+ handleAction(() => onStop(server.id));
184
+ }}
185
+ disabled={loading}
186
+ variant="danger"
187
+ />
188
+ <ActionButton
189
+ icon={X}
190
+ label="Cancel"
191
+ onClick={() => setConfirmingStop(false)}
192
+ disabled={loading}
193
+ />
194
+ </>
195
+ ) : (
196
+ <>
197
+ <ActionButton
198
+ icon={Square}
199
+ label="Stop"
200
+ onClick={() => setConfirmingStop(true)}
201
+ disabled={loading}
202
+ variant="danger"
203
+ />
204
+ <ActionButton
205
+ icon={RefreshCw}
206
+ label="Restart"
207
+ onClick={() => handleAction(() => onRestart(server.id))}
208
+ disabled={loading}
209
+ />
210
+ <ActionButton
211
+ icon={FileText}
212
+ label="Logs"
213
+ onClick={() => onViewLogs(server.id)}
214
+ disabled={loading}
215
+ />
216
+ </>
217
+ )}
218
+ </>
219
+ )}
220
+
221
+ {isStopped && (
222
+ <>
223
+ <ActionButton
224
+ icon={Play}
225
+ label="Start"
226
+ onClick={() => handleAction(() => onStart(server.id))}
227
+ disabled={loading}
228
+ variant="primary"
229
+ />
230
+ <ActionButton
231
+ icon={Trash2}
232
+ label="Remove"
233
+ onClick={() => handleAction(() => onRemove(server.id))}
234
+ disabled={loading}
235
+ variant="danger"
236
+ />
237
+ </>
238
+ )}
239
+
240
+ {isFailed && (
241
+ <>
242
+ <ActionButton
243
+ icon={Play}
244
+ label="Start"
245
+ onClick={() => handleAction(() => onStart(server.id))}
246
+ disabled={loading}
247
+ variant="primary"
248
+ />
249
+ <ActionButton
250
+ icon={FileText}
251
+ label="Logs"
252
+ onClick={() => onViewLogs(server.id)}
253
+ disabled={loading}
254
+ />
255
+ <ActionButton
256
+ icon={Trash2}
257
+ label="Remove"
258
+ onClick={() => handleAction(() => onRemove(server.id))}
259
+ disabled={loading}
260
+ variant="danger"
261
+ />
262
+ </>
263
+ )}
264
+
265
+ {server.status === "starting" && (
266
+ <span className="text-muted-foreground flex items-center gap-1 text-xs">
267
+ <RefreshCw className="h-3 w-3 animate-spin" />
268
+ Starting...
269
+ </span>
270
+ )}
271
+ </div>
272
+ </div>
273
+ );
274
+ }
275
+
276
+ interface ActionButtonProps {
277
+ icon: React.ComponentType<{ className?: string }>;
278
+ label: string;
279
+ onClick: () => void;
280
+ disabled?: boolean;
281
+ variant?: "default" | "primary" | "danger";
282
+ }
283
+
284
+ function ActionButton({
285
+ icon: Icon,
286
+ label,
287
+ onClick,
288
+ disabled,
289
+ variant = "default",
290
+ }: ActionButtonProps) {
291
+ return (
292
+ <button
293
+ onClick={onClick}
294
+ disabled={disabled}
295
+ title={label}
296
+ className={cn(
297
+ "flex h-7 items-center gap-1 rounded px-1.5",
298
+ "text-xs font-medium transition-colors",
299
+ "disabled:cursor-not-allowed disabled:opacity-50",
300
+ variant === "primary" &&
301
+ "bg-primary/10 text-primary hover:bg-primary/20",
302
+ variant === "danger" &&
303
+ "bg-red-500/10 text-red-500 hover:bg-red-500/20",
304
+ variant === "default" && "bg-muted/50 text-foreground hover:bg-muted"
305
+ )}
306
+ >
307
+ <Icon className="h-3.5 w-3.5" />
308
+ <span className="hidden sm:inline">{label}</span>
309
+ </button>
310
+ );
311
+ }
@@ -0,0 +1,91 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import { ChevronDown, Server } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+ import { DevServerCard } from "./DevServerCard";
7
+ import type { DevServer, Project } from "@/lib/db";
8
+
9
+ interface DevServersSectionProps {
10
+ servers: DevServer[];
11
+ projects: Project[];
12
+ onStart: (id: string) => Promise<void>;
13
+ onStop: (id: string) => Promise<void>;
14
+ onRestart: (id: string) => Promise<void>;
15
+ onRemove: (id: string) => Promise<void>;
16
+ onViewLogs: (id: string) => void;
17
+ }
18
+
19
+ export function DevServersSection({
20
+ servers,
21
+ projects,
22
+ onStart,
23
+ onStop,
24
+ onRestart,
25
+ onRemove,
26
+ onViewLogs,
27
+ }: DevServersSectionProps) {
28
+ const [expanded, setExpanded] = useState(true);
29
+
30
+ if (servers.length === 0) return null;
31
+
32
+ // Count running servers
33
+ const runningCount = servers.filter((s) => s.status === "running").length;
34
+
35
+ // Create project lookup map
36
+ const projectMap = new Map(projects.map((p) => [p.id, p]));
37
+
38
+ return (
39
+ <div className="border-border/50 border-b">
40
+ {/* Header */}
41
+ <button
42
+ onClick={() => setExpanded(!expanded)}
43
+ className={cn(
44
+ "flex w-full items-center gap-2 px-3 py-2",
45
+ "hover:bg-muted/50 transition-colors"
46
+ )}
47
+ >
48
+ <ChevronDown
49
+ className={cn(
50
+ "text-muted-foreground h-4 w-4 transition-transform",
51
+ !expanded && "-rotate-90"
52
+ )}
53
+ />
54
+ <Server className="text-muted-foreground h-4 w-4" />
55
+ <span className="flex-1 text-left text-sm font-medium">
56
+ Dev Servers
57
+ </span>
58
+ {runningCount > 0 && (
59
+ <span
60
+ className={cn(
61
+ "rounded-full px-1.5 py-0.5",
62
+ "text-[10px] font-medium",
63
+ "bg-green-500/20 text-green-500"
64
+ )}
65
+ >
66
+ {runningCount} running
67
+ </span>
68
+ )}
69
+ <span className="text-muted-foreground text-xs">{servers.length}</span>
70
+ </button>
71
+
72
+ {/* Server list */}
73
+ {expanded && (
74
+ <div className="space-y-2 px-3 pb-3">
75
+ {servers.map((server) => (
76
+ <DevServerCard
77
+ key={server.id}
78
+ server={server}
79
+ projectName={projectMap.get(server.project_id)?.name}
80
+ onStart={onStart}
81
+ onStop={onStop}
82
+ onRestart={onRestart}
83
+ onRemove={onRemove}
84
+ onViewLogs={onViewLogs}
85
+ />
86
+ ))}
87
+ </div>
88
+ )}
89
+ </div>
90
+ );
91
+ }
@@ -0,0 +1,151 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { X, RefreshCw, Loader2 } from "lucide-react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ interface ServerLogsModalProps {
8
+ serverId: string;
9
+ serverName: string;
10
+ onClose: () => void;
11
+ }
12
+
13
+ export function ServerLogsModal({
14
+ serverId,
15
+ serverName,
16
+ onClose,
17
+ }: ServerLogsModalProps) {
18
+ const [logs, setLogs] = useState<string[]>([]);
19
+ const [loading, setLoading] = useState(true);
20
+ const [refreshing, setRefreshing] = useState(false);
21
+ const logsRef = useRef<HTMLDivElement>(null);
22
+
23
+ const fetchLogs = async (isRefresh = false) => {
24
+ if (isRefresh) {
25
+ setRefreshing(true);
26
+ } else {
27
+ setLoading(true);
28
+ }
29
+
30
+ try {
31
+ const res = await fetch(`/api/dev-servers/${serverId}/logs?lines=200`);
32
+ if (res.ok) {
33
+ const data = await res.json();
34
+ setLogs(data.logs || []);
35
+ }
36
+ } catch (err) {
37
+ console.error("Failed to fetch logs:", err);
38
+ } finally {
39
+ setLoading(false);
40
+ setRefreshing(false);
41
+ }
42
+ };
43
+
44
+ // Initial fetch
45
+ useEffect(() => {
46
+ fetchLogs();
47
+ }, [serverId]);
48
+
49
+ // Auto-scroll to bottom when logs update
50
+ useEffect(() => {
51
+ if (logsRef.current) {
52
+ logsRef.current.scrollTop = logsRef.current.scrollHeight;
53
+ }
54
+ }, [logs]);
55
+
56
+ // Auto-refresh every 3 seconds
57
+ useEffect(() => {
58
+ const interval = setInterval(() => {
59
+ fetchLogs(true);
60
+ }, 3000);
61
+ return () => clearInterval(interval);
62
+ }, [serverId]);
63
+
64
+ return (
65
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4">
66
+ <div
67
+ className={cn(
68
+ "flex h-[80vh] w-full max-w-3xl flex-col rounded-xl",
69
+ "bg-background border-border border",
70
+ "shadow-2xl"
71
+ )}
72
+ >
73
+ {/* Header */}
74
+ <div className="border-border flex items-center justify-between border-b px-4 py-3">
75
+ <h2 className="truncate text-lg font-semibold">Logs: {serverName}</h2>
76
+ <div className="flex items-center gap-2">
77
+ <button
78
+ onClick={() => fetchLogs(true)}
79
+ disabled={refreshing}
80
+ className={cn(
81
+ "hover:bg-muted rounded-md p-1.5 transition-colors",
82
+ "disabled:opacity-50"
83
+ )}
84
+ title="Refresh"
85
+ >
86
+ <RefreshCw
87
+ className={cn("h-4 w-4", refreshing && "animate-spin")}
88
+ />
89
+ </button>
90
+ <button
91
+ onClick={onClose}
92
+ className="hover:bg-muted rounded-md p-1.5 transition-colors"
93
+ >
94
+ <X className="h-5 w-5" />
95
+ </button>
96
+ </div>
97
+ </div>
98
+
99
+ {/* Logs content */}
100
+ <div
101
+ ref={logsRef}
102
+ className={cn(
103
+ "flex-1 overflow-auto p-4",
104
+ "bg-zinc-950 font-mono text-sm leading-relaxed"
105
+ )}
106
+ >
107
+ {loading ? (
108
+ <div className="flex h-full items-center justify-center">
109
+ <Loader2 className="text-muted-foreground h-5 w-5 animate-spin" />
110
+ <span className="text-muted-foreground ml-2">
111
+ Loading logs...
112
+ </span>
113
+ </div>
114
+ ) : logs.length === 0 ? (
115
+ <div className="text-muted-foreground flex h-full items-center justify-center">
116
+ No logs available
117
+ </div>
118
+ ) : (
119
+ <div className="space-y-0.5">
120
+ {logs.map((line, i) => (
121
+ <div
122
+ key={i}
123
+ className={cn(
124
+ "break-all whitespace-pre-wrap",
125
+ line.includes("error") || line.includes("Error")
126
+ ? "text-red-400"
127
+ : line.includes("warn") || line.includes("Warning")
128
+ ? "text-yellow-400"
129
+ : "text-zinc-300"
130
+ )}
131
+ >
132
+ {line || " "}
133
+ </div>
134
+ ))}
135
+ </div>
136
+ )}
137
+ </div>
138
+
139
+ {/* Footer */}
140
+ <div className="border-border text-muted-foreground border-t px-4 py-2 text-xs">
141
+ Auto-refreshing every 3 seconds
142
+ {refreshing && (
143
+ <span className="ml-2">
144
+ <RefreshCw className="inline h-3 w-3 animate-spin" />
145
+ </span>
146
+ )}
147
+ </div>
148
+ </div>
149
+ </div>
150
+ );
151
+ }