Add streaming log viewer with service switching and filtering (step 6)

ddev logs -f runs indefinitely rather than completing, so it doesn't
fit runStreamed's resolve/reject-on-exit model or the terminal panel's
operation semantics — added a parallel startLogStream/logs:data path
in commandRunner.ts that shares the same process-tracking map (so
stopping a log stream reuses the existing terminal:cancel IPC) but
pushes chunks over a dedicated channel decoupled from the status
bar/toast system. Kill all tracked processes on app quit so a
forgotten open log viewer doesn't leave an orphaned `ddev logs -f`.

Two real bugs found and fixed via live testing:
- react-hooks/set-state-in-effect flagged synchronous setState calls
  used only to reset state on service change. Fixed by keying the
  streaming component by service (LogPane key={service}) so switching
  services remounts it and state resets via useState initializers
  instead — the React-recommended pattern for this.
- Filtering operated on raw stream chunks, not lines: a single data
  chunk can bundle many log lines or split one across chunk
  boundaries, so filtering by chunk let unrelated lines through
  whenever a match happened to share a chunk. Fixed by buffering
  partial lines per stream and only filtering/rendering once complete
  lines are assembled.

Verified end-to-end against the real scratch project via CDP-driven
clicks: live streaming from real containers (db and web service logs
both confirmed with distinct real content), service switching,
filtering (confirmed both the false-positive case is fixed and real
matches still work), and confirmed closing the viewer actually kills
the underlying `ddev logs -f` process rather than leaving it orphaned.
This commit is contained in:
R3ap3R
2026-08-03 00:13:49 -05:00
parent ebfec5f787
commit 49293ac34c
8 changed files with 276 additions and 1 deletions
+9
View File
@@ -6,6 +6,8 @@ import { registerProjectsIpc } from './ipc/projects'
import { registerTerminalIpc } from './ipc/terminal'
import { registerDatabaseIpc } from './ipc/database'
import { registerAddonsIpc } from './ipc/addons'
import { registerLogsIpc } from './ipc/logs'
import { killAllRunningCommands } from './commandRunner'
function createWindow(): void {
// Create the browser window.
@@ -58,6 +60,7 @@ app.whenReady().then(() => {
registerTerminalIpc()
registerDatabaseIpc()
registerAddonsIpc()
registerLogsIpc()
createWindow()
@@ -77,5 +80,11 @@ app.on('window-all-closed', () => {
}
})
// Kill any still-running ddev processes (e.g. an open `ddev logs -f` stream)
// so they don't linger as orphans after the app exits.
app.on('before-quit', () => {
killAllRunningCommands()
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.