Load complete background process output and fix dialog layout
This commit is contained in:
@@ -166,7 +166,7 @@ export class BackgroundProcessManager {
|
|||||||
async readOutput(
|
async readOutput(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
processId: string,
|
processId: string,
|
||||||
options: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number },
|
options: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number; maxBytes?: number },
|
||||||
) {
|
) {
|
||||||
const outputPath = this.getOutputPath(workspaceId, processId)
|
const outputPath = this.getOutputPath(workspaceId, processId)
|
||||||
if (!existsSync(outputPath)) {
|
if (!existsSync(outputPath)) {
|
||||||
@@ -178,7 +178,7 @@ export class BackgroundProcessManager {
|
|||||||
const method = options.method ?? "full"
|
const method = options.method ?? "full"
|
||||||
const lineCount = options.lines ?? 10
|
const lineCount = options.lines ?? 10
|
||||||
|
|
||||||
const raw = await this.readOutputBytes(outputPath, sizeBytes)
|
const raw = await this.readOutputBytes(outputPath, sizeBytes, options.maxBytes)
|
||||||
let content = raw
|
let content = raw
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
@@ -198,10 +198,11 @@ export class BackgroundProcessManager {
|
|||||||
content = raw
|
content = raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const effectiveMaxBytes = options.maxBytes
|
||||||
return {
|
return {
|
||||||
id: processId,
|
id: processId,
|
||||||
content,
|
content,
|
||||||
truncated: sizeBytes > MAX_OUTPUT_BYTES,
|
truncated: effectiveMaxBytes !== undefined && sizeBytes > effectiveMaxBytes,
|
||||||
sizeBytes,
|
sizeBytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -280,12 +281,12 @@ export class BackgroundProcessManager {
|
|||||||
return "error"
|
return "error"
|
||||||
}
|
}
|
||||||
|
|
||||||
private async readOutputBytes(outputPath: string, sizeBytes: number): Promise<string> {
|
private async readOutputBytes(outputPath: string, sizeBytes: number, maxBytes?: number): Promise<string> {
|
||||||
if (sizeBytes <= MAX_OUTPUT_BYTES) {
|
if (maxBytes === undefined || sizeBytes <= maxBytes) {
|
||||||
return await fs.readFile(outputPath, "utf-8")
|
return await fs.readFile(outputPath, "utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = Math.max(0, sizeBytes - MAX_OUTPUT_BYTES)
|
const start = Math.max(0, sizeBytes - maxBytes)
|
||||||
const file = await fs.open(outputPath, "r")
|
const file = await fs.open(outputPath, "r")
|
||||||
const buffer = Buffer.alloc(sizeBytes - start)
|
const buffer = Buffer.alloc(sizeBytes - start)
|
||||||
await file.read(buffer, 0, buffer.length, start)
|
await file.read(buffer, 0, buffer.length, start)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const OutputQuerySchema = z.object({
|
|||||||
mode: z.enum(["full", "tail", "head", "grep"]).optional(),
|
mode: z.enum(["full", "tail", "head", "grep"]).optional(),
|
||||||
pattern: z.string().optional(),
|
pattern: z.string().optional(),
|
||||||
lines: z.coerce.number().int().positive().max(2000).optional(),
|
lines: z.coerce.number().int().positive().max(2000).optional(),
|
||||||
|
maxBytes: z.coerce.number().int().positive().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: RouteDeps) {
|
export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: RouteDeps) {
|
||||||
@@ -66,6 +67,7 @@ export function registerBackgroundProcessRoutes(app: FastifyInstance, deps: Rout
|
|||||||
method,
|
method,
|
||||||
pattern: query.pattern,
|
pattern: query.pattern,
|
||||||
lines: query.lines,
|
lines: query.lines,
|
||||||
|
maxBytes: query.maxBytes,
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
reply.code(400)
|
reply.code(400)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
|
|||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
serverApi
|
serverApi
|
||||||
.fetchBackgroundProcessOutput(props.instanceId, process.id, { method: "full" })
|
.fetchBackgroundProcessOutput(props.instanceId, process.id, { method: "full", maxBytes: undefined })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!active) return
|
if (!active) return
|
||||||
setOutput(response.content)
|
setOutput(response.content)
|
||||||
@@ -65,18 +65,20 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
|
|||||||
<Dialog.Overlay class="modal-overlay" />
|
<Dialog.Overlay class="modal-overlay" />
|
||||||
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
<div class="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||||
<Dialog.Content class="modal-surface w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden">
|
<Dialog.Content class="modal-surface w-full max-w-5xl max-h-[90vh] flex flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between px-6 py-4 border-b border-base">
|
<div class="flex items-start justify-between px-6 py-4 border-b border-base gap-4">
|
||||||
<div class="flex flex-col">
|
<div class="flex-1 min-w-0">
|
||||||
<Dialog.Title class="text-lg font-semibold text-primary">Background Output</Dialog.Title>
|
<Dialog.Title class="text-lg font-semibold text-primary">Background Output</Dialog.Title>
|
||||||
<Show when={props.process}>
|
<Show when={props.process}>
|
||||||
<span class="text-xs text-secondary">
|
<span class="text-xs text-secondary block">
|
||||||
{props.process?.title} · {props.process?.id}
|
{props.process?.title} · {props.process?.id}
|
||||||
</span>
|
</span>
|
||||||
<span class="text-xs text-secondary mt-1 break-words">{props.process?.command}</span>
|
<span class="text-xs text-secondary mt-1 block truncate" title={props.process?.command}>
|
||||||
</Show>
|
{props.process?.command}
|
||||||
</div>
|
</span>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="button" class="button-tertiary" onClick={props.onClose}>
|
<button type="button" class="button-tertiary flex-shrink-0" onClick={props.onClose}>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,7 +90,7 @@ export function BackgroundProcessOutputDialog(props: BackgroundProcessOutputDial
|
|||||||
<Show when={truncated()}>
|
<Show when={truncated()}>
|
||||||
<p class="text-xs text-secondary mb-2">Output truncated for display.</p>
|
<p class="text-xs text-secondary mb-2">Output truncated for display.</p>
|
||||||
</Show>
|
</Show>
|
||||||
<pre class="text-xs whitespace-pre-wrap break-words text-primary bg-surface-secondary border border-base rounded-md p-4">
|
<pre class="text-xs whitespace-pre-wrap break-all text-primary bg-surface-secondary border border-base rounded-md p-4 font-mono">
|
||||||
{output()}
|
{output()}
|
||||||
</pre>
|
</pre>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ export const serverApi = {
|
|||||||
fetchBackgroundProcessOutput(
|
fetchBackgroundProcessOutput(
|
||||||
instanceId: string,
|
instanceId: string,
|
||||||
processId: string,
|
processId: string,
|
||||||
options?: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number },
|
options?: { method?: "full" | "tail" | "head" | "grep"; pattern?: string; lines?: number; maxBytes?: number },
|
||||||
): Promise<BackgroundProcessOutputResponse> {
|
): Promise<BackgroundProcessOutputResponse> {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
if (options?.method) {
|
if (options?.method) {
|
||||||
@@ -259,6 +259,9 @@ export const serverApi = {
|
|||||||
if (options?.lines) {
|
if (options?.lines) {
|
||||||
params.set("lines", String(options.lines))
|
params.set("lines", String(options.lines))
|
||||||
}
|
}
|
||||||
|
if (options?.maxBytes !== undefined) {
|
||||||
|
params.set("maxBytes", String(options.maxBytes))
|
||||||
|
}
|
||||||
const query = params.toString()
|
const query = params.toString()
|
||||||
const suffix = query ? `?${query}` : ""
|
const suffix = query ? `?${query}` : ""
|
||||||
return request<BackgroundProcessOutputResponse>(
|
return request<BackgroundProcessOutputResponse>(
|
||||||
|
|||||||
Reference in New Issue
Block a user