-
Notifications
You must be signed in to change notification settings - Fork 0
Kestra end to end integration with the frontend and backend #37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThe changes introduce output streaming and reporting capabilities across the stack. The backend webhook now includes task output data, the frontend WebSocket handler captures it, a new ScanReport component displays multi-tab security reports with export functionality, and orchestration flows transmit actual task outputs to webhooks for display. Changes
Sequence Diagram(s)sequenceDiagram
participant Kestra as Kestra Orchestration
participant Backend as Backend API
participant WS as WebSocket Server
participant Frontend as Frontend Handler
participant State as React State
participant UI as ScanReport UI
Kestra->>Kestra: Execute task (adversary, summarizer, etc.)
Kestra->>Backend: POST webhook with task output
note over Backend: Includes output field in payload
Backend->>WS: Broadcast task_update with output
note over WS: Payload contains output data
WS->>Frontend: WebSocket message (task_update)
Frontend->>Frontend: Parse WebSocketMessage<br/>Extract output field
Frontend->>Frontend: console.log received output
Frontend->>State: Call onOutputReceived(taskId, output)
State->>State: Update scanOutputs[taskId] = output
State->>UI: Re-render with updated outputs
UI->>UI: Display in active tab<br/>Enable corresponding tab
UI->>UI: Render formatted output or placeholder
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20–25 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/app/api/webhooks.py (1)
43-51: Consider validating output size before broadcasting.The
outputfield is broadcasted without size validation. Large outputs from AI agents could overwhelm WebSocket clients or cause memory issues.Apply this diff to add size validation:
await manager.broadcast_update(execution_id, { "type": "task_update", "execution_id": execution_id, "task_id": task_id, "status": status, "message": message, - "output": data.get("output"), + "output": data.get("output") if (output := data.get("output")) and len(str(output)) < 1_000_000 else None, "data": data.get("data") })Alternatively, log the output size for monitoring:
+ output = data.get("output") + if output: + print(f"📊 Output size: {len(str(output))} bytes") + await manager.broadcast_update(execution_id, { "type": "task_update", "execution_id": execution_id, "task_id": task_id, "status": status, "message": message, - "output": data.get("output"), + "output": output, "data": data.get("data") })
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
backend/app/api/webhooks.py(1 hunks)frontend/Dockerfile.dev(1 hunks)frontend/app/api.ts(2 hunks)frontend/app/components/Icon.tsx(1 hunks)frontend/app/dashboard/components/ExecutionStatus.tsx(6 hunks)frontend/app/dashboard/components/ScanReport.tsx(1 hunks)frontend/app/dashboard/page.tsx(4 hunks)frontend/app/types.ts(1 hunks)kestra/flows/redloop_orchestrator_v1.yaml(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/app/dashboard/components/ScanReport.tsx (2)
frontend/app/components/UI.tsx (2)
Card(200-230)Button(241-299)frontend/app/components/Icon.tsx (1)
Icon(12-74)
frontend/app/dashboard/page.tsx (2)
frontend/app/dashboard/components/ScanReport.tsx (1)
ScanReport(32-233)frontend/app/dashboard/components/ExecutionStatus.tsx (1)
ExecutionStatus(22-179)
🪛 Biome (2.1.2)
frontend/app/dashboard/components/ScanReport.tsx
[error] 160-172: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🔇 Additional comments (15)
frontend/app/components/Icon.tsx (1)
52-55: LGTM!The download icon addition follows the existing pattern and the SVG path looks correct.
frontend/app/types.ts (1)
14-15: LGTM!The type extension correctly aligns with the new download icon added to Icon.tsx.
frontend/app/api.ts (1)
48-48: LGTM!The optional
outputfield addition aligns with the backend changes and enables per-task output propagation.frontend/app/dashboard/page.tsx (3)
27-30: LGTM!The dynamic import of ScanReport follows the existing pattern for lazy-loaded components.
223-238: LGTM!The state management for scan outputs is well-structured. The
handleOutputReceivedcallback is properly memoized and the state initialization inhandleScanStartensures a clean slate for each scan.
295-316: LGTM!The integration of ScanReport with ExecutionStatus is well-structured. The
onOutputReceivedcallback properly propagates per-task outputs, and the conditional rendering ensures ScanReport only appears when there's an active execution.kestra/flows/redloop_orchestrator_v1.yaml (1)
100-141: Excellent prompt improvements to reduce false positives.The updated adversary prompt enforces strict validation rules requiring exact code references and file paths. This significantly reduces the risk of hallucinated vulnerabilities. The structured JSON output format ensures consistent parsing downstream.
Note: The strict requirements may reduce recall—real vulnerabilities might be missed if the AI cannot provide exact code quotes. Consider monitoring for cases where legitimate issues are not reported due to the constraints.
frontend/app/dashboard/components/ExecutionStatus.tsx (3)
19-19: LGTM!The
onOutputReceivedcallback prop is correctly implemented with ref-based handling to avoid stale closures. The ref is properly synchronized in the effect.Also applies to: 22-22, 31-31, 36-36
56-63: LGTM!The output handling logic properly validates the output (string type, non-empty) before invoking the callback. The line count summary log provides helpful feedback without exposing sensitive data.
91-95: LGTM!The UI text improvements make the workflow stages clearer and more user-friendly.
Also applies to: 115-115
frontend/app/dashboard/components/ScanReport.tsx (5)
35-55: LGTM!The JSON download function correctly constructs the report, creates a blob, and cleans up the object URL. The approach is standard for client-side file downloads.
57-103: LGTM!The Markdown export provides a well-formatted security assessment document. Fallback text for missing sections ensures the export always succeeds.
109-125: LGTM!The loading state provides clear feedback during scanning with an animated spinner and descriptive message.
183-211: LGTM!The content area with AnimatePresence provides smooth tab transitions. The conditional rendering handles both empty states appropriately with helpful messages.
214-230: LGTM!The footer provides useful execution tracking and visual indicators for completion status of each analysis stage.
| // Ignore ping/pong keep-alive messages | ||
| if (event.data === 'pong') return; | ||
| try { | ||
| onMessage(JSON.parse(event.data) as WebSocketMessage); | ||
| const parsed = JSON.parse(event.data) as WebSocketMessage; | ||
| console.log('📡 WebSocket message received:', parsed); | ||
| console.log('📡 Output field:', parsed.output ? `${parsed.output.substring(0, 100)}...` : 'NONE'); | ||
| onMessage(parsed); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider security implications of logging output data.
The console logs output data which could contain sensitive information (API keys, credentials, PII) found during vulnerability scans. This could expose sensitive data in browser developer tools.
Apply this diff to sanitize logs or make them conditional:
try {
const parsed = JSON.parse(event.data) as WebSocketMessage;
- console.log('📡 WebSocket message received:', parsed);
- console.log('📡 Output field:', parsed.output ? `${parsed.output.substring(0, 100)}...` : 'NONE');
+ if (process.env.NODE_ENV === 'development') {
+ console.log('📡 WebSocket message received:', { ...parsed, output: parsed.output ? '[REDACTED]' : undefined });
+ }
onMessage(parsed);🤖 Prompt for AI Agents
In frontend/app/api.ts around lines 102 to 108, the code logs parsed WebSocket
payloads including parsed.output which may contain sensitive data; replace these
direct console logs with safe logging: stop logging the full parsed message and
the raw output, and instead log only non-sensitive metadata (e.g., message type,
length) and at most the first N characters with redaction of likely secrets
(tokens, keys, emails) or a fixed placeholder; make verbose output conditional
on a dev-only flag (NODE_ENV !== 'production' or a dedicated feature flag) so
production builds never emit raw payloads.
| <button | ||
| key={tab.id} | ||
| onClick={() => setActiveTab(tab.id)} | ||
| disabled={!hasOutput} | ||
| className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive | ||
| ? 'border-current bg-white/5' | ||
| : 'border-transparent hover:bg-white/5' | ||
| } ${hasOutput | ||
| ? 'text-white cursor-pointer' | ||
| : 'text-[#404050] cursor-not-allowed' | ||
| }`} | ||
| style={{ color: isActive ? tab.color : undefined }} | ||
| > | ||
| <span>{tab.icon}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add explicit type="button" to tab buttons.
The tab buttons lack an explicit type attribute. While these buttons are not inside a form, adding type="button" is a best practice to prevent unintended form submissions if the component is ever used within a form context.
Apply this diff:
<button
key={tab.id}
+ type="button"
onClick={() => setActiveTab(tab.id)}
disabled={!hasOutput}
className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActiveBased on learnings, static analysis hints about button types should be addressed.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| key={tab.id} | |
| onClick={() => setActiveTab(tab.id)} | |
| disabled={!hasOutput} | |
| className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive | |
| ? 'border-current bg-white/5' | |
| : 'border-transparent hover:bg-white/5' | |
| } ${hasOutput | |
| ? 'text-white cursor-pointer' | |
| : 'text-[#404050] cursor-not-allowed' | |
| }`} | |
| style={{ color: isActive ? tab.color : undefined }} | |
| > | |
| <span>{tab.icon}</span> | |
| <button | |
| key={tab.id} | |
| type="button" | |
| onClick={() => setActiveTab(tab.id)} | |
| disabled={!hasOutput} | |
| className={`flex-shrink-0 px-5 py-3 flex items-center gap-2 text-sm font-medium transition-all border-b-2 ${isActive | |
| ? 'border-current bg-white/5' | |
| : 'border-transparent hover:bg-white/5' | |
| } ${hasOutput | |
| ? 'text-white cursor-pointer' | |
| : 'text-[#404050] cursor-not-allowed' | |
| }`} | |
| style={{ color: isActive ? tab.color : undefined }} | |
| > | |
| <span>{tab.icon}</span> |
🧰 Tools
🪛 Biome (2.1.2)
[error] 160-172: Provide an explicit type prop for the button element.
The default type of a button is submit, which causes the submission of a form when placed inside a form element. This is likely not the behaviour that you want inside a React application.
Allowed button types are: submit, button or reset
(lint/a11y/useButtonType)
🤖 Prompt for AI Agents
In frontend/app/dashboard/components/ScanReport.tsx around lines 159 to 172, the
tab <button> elements are missing an explicit type attribute; add type="button"
to the button JSX so the tabs cannot accidentally submit a surrounding form if
this component is ever used inside a form context, and ensure the attribute is
applied alongside the existing props (key, onClick, disabled, className, style).
| WORKDIR /app | ||
|
|
||
| COPY package.json package-lock.json* ./ | ||
| COPY package.json package-lock.json* .npmrc* ./ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid copying .npmrc into the image layer if it can contain auth tokens/secrets.
COPY ... .npmrc* ... can bake private registry credentials into a Docker layer (and leak via image sharing/layer caching). Prefer BuildKit secrets for .npmrc, or ensure the repo .npmrc is tokenless (env-var substitution only) and that secrets are injected at build/run time.
Suggested approach (BuildKit secret):
-COPY package.json package-lock.json* .npmrc* ./
+COPY package.json package-lock.json* ./
RUN --mount=type=cache,target=/root/.npm \
- npm ci
+ --mount=type=secret,id=npmrc,target=/root/.npmrc \
+ npm ci(Then build with --secret id=npmrc,src=$HOME/.npmrc or equivalent.)
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In frontend/Dockerfile.dev around line 5, the Dockerfile currently copies .npmrc
into the image which may bake auth tokens into a layer; remove .npmrc (and any
glob that matches it) from the COPY line so it is not added to the image, and
instead switch to using BuildKit secrets (pass your local .npmrc with --secret
id=npmrc,src=~/.npmrc at build time) or ensure the repository .npmrc contains no
secrets and uses env-var substitution at runtime; update the Dockerfile to read
the secret at build time via the BuildKit secret mechanism or otherwise inject
credentials at container run time rather than copying the file into an image
layer.
| contentType: application/json | ||
| body: | | ||
| {"execution_id": "{{ execution.id }}", "task_id": "adversary_kestra", "status": "SUCCESS", "message": "🔴 Adversary scan complete - found vulnerabilities"} | ||
| {"execution_id": "{{ execution.id }}", "task_id": "adversary_kestra", "status": "SUCCESS", "message": "🔴 Adversary scan complete", "output": {{ outputs.adversary_kestra.textOutput | default('"No output"') | json }}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
Kestra json filter escaping nested quotes special characters
💡 Result:
Short answer: use Kestra/Pebble's toJson or jq(@JSON) to produce properly escaped JSON strings, and use bracket notation for keys with special characters.
Examples:
- Escape a string value in a template: {{ myString | toJson }} — toJson wraps in quotes and escapes " and . (kestra.io) [1]
- Produce a JSON-escaped string with jq: {{ myString | jq('@JSON') | first }} — jq's @JSON emits a JSON-quoted/escaped representation (use first because jq returns an array in expressions). (kestra.io) [2][1]
- Access keys that contain dots, hyphens or quotes using bracket notation: {{ outputs.task['weird-key'] }} or {{ obj['key.with.dots'] }}. (kestra.io) [1]
Notes on nested quoting inside HTTP bodies:
- Build the body using toJson or jq to avoid manual escaping. Example:
message: "{{ { text: myString } | toJson }}"
or
body: "{{ { text: (myString | jq('@JSON') | first) } | toJson }}" — both ensure nested quotes and control chars are escaped for JSON. (kestra.io) [1][2]
If you want, paste a specific failing example and I’ll show the exact template with escaping.
Citations:
- 1: https://kestra.io/docs/concepts/expression?utm_source=openai
- 2: https://kestra.io/docs/how-to-guides/json?utm_source=openai
- 3: https://kestra.io/docs/concepts/expression?utm_source=openai
- 4: https://kestra.io/docs/concepts/expression?utm_source=openai
Use toJson or jq('@json') instead of | json for proper JSON escaping.
The webhook bodies should use Kestra's standard JSON filters to handle nested quotes and special characters. Replace {{ outputs.X.textOutput | default('"No output"') | json }} with {{ outputs.X.textOutput | default('No output') | toJson }} (or {{ outputs.X.textOutput | default('No output') | jq('@json') | first }}). The | json filter is not a documented Kestra filter; use toJson to properly escape strings within JSON context. Also remove literal quotes from the default value—use plain strings and let toJson handle the escaping.
🤖 Prompt for AI Agents
In kestra/flows/redloop_orchestrator_v1.yaml around line 150, the template uses
the nonstandard "| json" filter and includes literal quotes in the default value
which can produce incorrect escaping; replace the expression to use Kestra's
toJson (or jq('@json') | first) and remove the extra literal quotes in the
default string so it becomes default('No output') piped into toJson to ensure
proper JSON escaping of nested quotes/special characters.
#19
Summary by CodeRabbit
New Features
UI/UX Improvements
✏️ Tip: You can customize this high-level summary in your review settings.