Skip to content

Commit 4d0a676

Browse files
authored
Implement service control commands and enhance UI (#72)
* feat: Implement service control commands for starting, stopping, and restarting services - Added ServiceController to manage service lifecycle operations. - Implemented StartService, StopService, and RestartService methods. - Created bulk operations for starting, stopping, and restarting multiple services. - Introduced command-line interface commands for starting and stopping services. - Added operation manager to handle concurrent service operations with state management. - Implemented validation for service names and error handling for service operations. - Added tests for operation manager and service control functionalities. * feat: Enhance service management UI with error indicators, action buttons, and improved service operation logic * fix: Update fullscreen toggle behavior to show view mode and grid columns controls * Refactor service control commands to improve context handling and reduce code duplication - Consolidated signal handling setup into a reusable function `setupContextWithSignalHandling`. - Simplified service list parsing and validation by introducing `parseServiceList`. - Enhanced bulk operation confirmation with `confirmBulkOperation`. - Streamlined service operation execution with `executeServiceOperation` for consistent handling of single and bulk operations. - Improved error handling and messaging for service operations. - Removed redundant service name validation logic from multiple locations, utilizing the `security.ValidateServiceName` function instead. - Updated dashboard service operations to use request context for cancellation. * fix: Improve error messages for invalid output formats in health commands
1 parent 02330a3 commit 4d0a676

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+6408
-660
lines changed

cli/dashboard/design/components/toolbar-redesign.md

Lines changed: 522 additions & 0 deletions
Large diffs are not rendered by default.

cli/dashboard/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"test:e2e:ui": "playwright test --ui"
2020
},
2121
"dependencies": {
22+
"@radix-ui/react-dropdown-menu": "^2.1.16",
2223
"@radix-ui/react-slot": "^1.2.4",
2324
"ansi-to-html": "^0.7.2",
2425
"class-variance-authority": "^0.7.1",

cli/dashboard/pnpm-lock.yaml

Lines changed: 642 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cli/dashboard/src/components/LogsMultiPaneView.test.tsx

Lines changed: 53 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,11 @@ describe('LogsMultiPaneView - Fullscreen', () => {
5151
globalThis.WebSocket = WebSocketMock as unknown as typeof WebSocket
5252
})
5353

54-
it('should render fullscreen toggle button', async () => {
54+
it('should render fullscreen toggle button in toolbar', async () => {
5555
render(<LogsMultiPaneView />)
5656

5757
await waitFor(() => {
58-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
58+
expect(screen.getByTitle('Enter Fullscreen (F11)')).toBeInTheDocument()
5959
})
6060
})
6161

@@ -66,15 +66,14 @@ describe('LogsMultiPaneView - Fullscreen', () => {
6666
render(<LogsMultiPaneView onFullscreenChange={onFullscreenChange} />)
6767

6868
await waitFor(() => {
69-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
69+
expect(screen.getByTitle('Enter Fullscreen (F11)')).toBeInTheDocument()
7070
})
7171

72-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
72+
const fullscreenButton = screen.getByTitle('Enter Fullscreen (F11)')
7373
await user.click(fullscreenButton)
7474

7575
await waitFor(() => {
7676
expect(onFullscreenChange).toHaveBeenCalledWith(true)
77-
expect(screen.getByTitle(/Exit Fullscreen/)).toBeInTheDocument()
7877
})
7978
})
8079

@@ -85,28 +84,28 @@ describe('LogsMultiPaneView - Fullscreen', () => {
8584
render(<LogsMultiPaneView onFullscreenChange={onFullscreenChange} />)
8685

8786
await waitFor(() => {
88-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
87+
expect(screen.getByTitle('Enter Fullscreen (F11)')).toBeInTheDocument()
8988
})
9089

9190
// Enter fullscreen
92-
const enterButton = screen.getByTitle(/Enter Fullscreen/)
93-
await user.click(enterButton)
91+
await user.click(screen.getByTitle('Enter Fullscreen (F11)'))
9492

9593
await waitFor(() => {
96-
expect(screen.getByTitle(/Exit Fullscreen/)).toBeInTheDocument()
94+
expect(onFullscreenChange).toHaveBeenCalledWith(true)
9795
})
9896

9997
// Exit fullscreen
100-
const exitButton = screen.getByTitle(/Exit Fullscreen/)
101-
await user.click(exitButton)
98+
await waitFor(() => {
99+
expect(screen.getByTitle('Exit Fullscreen (F11)')).toBeInTheDocument()
100+
})
101+
await user.click(screen.getByTitle('Exit Fullscreen (F11)'))
102102

103103
await waitFor(() => {
104104
expect(onFullscreenChange).toHaveBeenCalledWith(false)
105-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
106105
})
107106
})
108107

109-
it('should hide view mode toggle in fullscreen', async () => {
108+
it('should show view mode toggle in fullscreen', async () => {
110109
const user = userEvent.setup()
111110

112111
render(<LogsMultiPaneView />)
@@ -116,31 +115,31 @@ describe('LogsMultiPaneView - Fullscreen', () => {
116115
expect(screen.getByText('Unified')).toBeInTheDocument()
117116
})
118117

119-
// Enter fullscreen
120-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
121-
await user.click(fullscreenButton)
118+
// Enter fullscreen via keyboard shortcut
119+
await user.keyboard('{F11}')
122120

121+
// View mode toggle should still be visible in fullscreen
123122
await waitFor(() => {
124-
expect(screen.queryByText('Grid')).not.toBeInTheDocument()
125-
expect(screen.queryByText('Unified')).not.toBeInTheDocument()
123+
expect(screen.getByText('Grid')).toBeInTheDocument()
124+
expect(screen.getByText('Unified')).toBeInTheDocument()
126125
})
127126
})
128127

129-
it('should hide settings button in fullscreen', async () => {
128+
it('should keep settings accessible in fullscreen', async () => {
130129
const user = userEvent.setup()
131130

132131
render(<LogsMultiPaneView />)
133132

134133
await waitFor(() => {
135-
expect(screen.getByTitle('Settings')).toBeInTheDocument()
134+
expect(screen.getByTitle('Settings (Ctrl+,)')).toBeInTheDocument()
136135
})
137136

138-
// Enter fullscreen
139-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
140-
await user.click(fullscreenButton)
137+
// Enter fullscreen via keyboard
138+
await user.keyboard('{F11}')
141139

140+
// Settings should still be accessible as direct button
142141
await waitFor(() => {
143-
expect(screen.getByTitle('Settings')).toBeInTheDocument()
142+
expect(screen.getByTitle('Settings (Ctrl+,)')).toBeInTheDocument()
144143
})
145144
})
146145

@@ -149,35 +148,36 @@ describe('LogsMultiPaneView - Fullscreen', () => {
149148

150149
render(<LogsMultiPaneView />)
151150

151+
// Export button should be present
152152
await waitFor(() => {
153-
expect(screen.getByTitle('Export All')).toBeInTheDocument()
153+
expect(screen.getByTitle('Export All Logs')).toBeInTheDocument()
154154
})
155-
155+
156156
// Enter fullscreen
157-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
158-
await user.click(fullscreenButton)
157+
await user.keyboard('{F11}')
159158

159+
// Export should not be visible in fullscreen
160160
await waitFor(() => {
161-
expect(screen.queryByTitle('Export All')).not.toBeInTheDocument()
161+
expect(screen.queryByTitle('Export All Logs')).not.toBeInTheDocument()
162162
})
163163
})
164164

165-
it('should keep service selector visible in fullscreen', async () => {
165+
it('should keep service buttons visible in fullscreen', async () => {
166166
const user = userEvent.setup()
167167

168168
render(<LogsMultiPaneView />)
169169

170+
// Check service buttons are present
170171
await waitFor(() => {
171-
expect(screen.getByText('Services')).toBeInTheDocument()
172+
expect(screen.getByTitle('Start All (Ctrl+Shift+S)')).toBeInTheDocument()
172173
})
173174

174-
// Enter fullscreen
175-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
176-
await user.click(fullscreenButton)
175+
// Enter fullscreen via keyboard
176+
await user.keyboard('{F11}')
177177

178-
// Service selector should still be visible in fullscreen
178+
// Service buttons should still be visible in fullscreen
179179
await waitFor(() => {
180-
expect(screen.getByText('Services')).toBeInTheDocument()
180+
expect(screen.getByTitle('Start All (Ctrl+Shift+S)')).toBeInTheDocument()
181181
})
182182
})
183183

@@ -187,15 +187,14 @@ describe('LogsMultiPaneView - Fullscreen', () => {
187187
render(<LogsMultiPaneView />)
188188

189189
await waitFor(() => {
190-
expect(screen.getByTitle(/Pause/)).toBeInTheDocument()
190+
expect(screen.getByRole('button', { name: /pause log stream/i })).toBeInTheDocument()
191191
})
192192

193-
// Enter fullscreen
194-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
195-
await user.click(fullscreenButton)
193+
// Enter fullscreen via keyboard
194+
await user.keyboard('{F11}')
196195

197196
await waitFor(() => {
198-
expect(screen.getByTitle(/Pause/)).toBeInTheDocument()
197+
expect(screen.getByRole('button', { name: /pause log stream/i })).toBeInTheDocument()
199198
})
200199
})
201200

@@ -206,7 +205,7 @@ describe('LogsMultiPaneView - Fullscreen', () => {
206205
render(<LogsMultiPaneView onFullscreenChange={onFullscreenChange} />)
207206

208207
await waitFor(() => {
209-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
208+
expect(screen.getByRole('toolbar')).toBeInTheDocument()
210209
})
211210

212211
// Press F11
@@ -224,7 +223,7 @@ describe('LogsMultiPaneView - Fullscreen', () => {
224223
render(<LogsMultiPaneView onFullscreenChange={onFullscreenChange} />)
225224

226225
await waitFor(() => {
227-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
226+
expect(screen.getByRole('toolbar')).toBeInTheDocument()
228227
})
229228

230229
// Press Ctrl+Shift+F
@@ -241,9 +240,8 @@ describe('LogsMultiPaneView - Fullscreen', () => {
241240

242241
render(<LogsMultiPaneView onFullscreenChange={onFullscreenChange} />)
243242

244-
// Enter fullscreen
245-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
246-
await user.click(fullscreenButton)
243+
// Enter fullscreen via keyboard
244+
await user.keyboard('{F11}')
247245

248246
await waitFor(() => {
249247
expect(onFullscreenChange).toHaveBeenCalledWith(true)
@@ -263,45 +261,39 @@ describe('LogsMultiPaneView - Fullscreen', () => {
263261
const { container } = render(<LogsMultiPaneView />)
264262

265263
await waitFor(() => {
266-
expect(screen.getByTitle(/Enter Fullscreen/)).toBeInTheDocument()
264+
expect(screen.getByRole('toolbar')).toBeInTheDocument()
267265
})
268266

269267
// Normal mode - should not have fixed positioning
270268
expect(container.querySelector('.fixed.inset-0.z-50')).not.toBeInTheDocument()
271269

272-
// Enter fullscreen
273-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
274-
await user.click(fullscreenButton)
270+
// Enter fullscreen via keyboard
271+
await user.keyboard('{F11}')
275272

276273
await waitFor(() => {
277274
// Fullscreen mode - should have fixed positioning
278275
expect(container.querySelector('.fixed.inset-0.z-50')).toBeInTheDocument()
279276
})
280277
})
281278

282-
it('should show Maximize icon when not fullscreen', async () => {
279+
it('should show fullscreen button in toolbar', async () => {
283280
render(<LogsMultiPaneView />)
284281

285282
await waitFor(() => {
286-
const button = screen.getByTitle(/Enter Fullscreen/)
287-
const svg = button.querySelector('svg')
288-
expect(svg).toBeInTheDocument()
283+
expect(screen.getByTitle('Enter Fullscreen (F11)')).toBeInTheDocument()
289284
})
290285
})
291286

292-
it('should show Minimize icon when fullscreen', async () => {
287+
it('should show exit fullscreen option when in fullscreen', async () => {
293288
const user = userEvent.setup()
294289

295290
render(<LogsMultiPaneView />)
296291

297-
// Enter fullscreen
298-
const fullscreenButton = screen.getByTitle(/Enter Fullscreen/)
299-
await user.click(fullscreenButton)
292+
// Enter fullscreen via keyboard
293+
await user.keyboard('{F11}')
300294

301295
await waitFor(() => {
302-
const button = screen.getByTitle(/Exit Fullscreen/)
303-
const svg = button.querySelector('svg')
304-
expect(svg).toBeInTheDocument()
296+
expect(screen.getByTitle('Exit Fullscreen (F11)')).toBeInTheDocument()
305297
})
306298
})
307299
})

0 commit comments

Comments
 (0)