-
Notifications
You must be signed in to change notification settings - Fork 43
feat: implement light mode and theme toggle functionality #406
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
Changes from 1 commit
11f37bf
b771071
74cd2d4
fa425e4
71df4d3
45c8943
c96e767
e7ad975
942960d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||||||||||||||||||||||||||
| import { NextRequest, NextResponse } from 'next/server'; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const API_BASE_URL = process.env.EXOSPHERE_STATE_MANAGER_URI || 'http://localhost:8000'; | ||||||||||||||||||||||||||||||||||||||||||
| const API_KEY = process.env.EXOSPHERE_API_KEY; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| export async function POST(request: NextRequest) { | ||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||
| const { searchParams } = new URL(request.url); | ||||||||||||||||||||||||||||||||||||||||||
| const namespace = searchParams.get('namespace'); | ||||||||||||||||||||||||||||||||||||||||||
| const stateId = searchParams.get('stateId'); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!namespace || !stateId) { | ||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'Namespace and stateId are required' }, { status: 400 }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+12
to
+14
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate namespace/stateId to prevent path abuse and tighten input contract Currently any string (including slashes or path traversal sequences) is accepted and interpolated into the downstream path. Constrain to a safe charset and reasonable length. Apply this diff: - if (!namespace || !stateId) {
- return NextResponse.json({ error: 'Namespace and stateId are required' }, { status: 400 });
- }
+ if (!namespace || !stateId) {
+ return NextResponse.json({ error: 'Namespace and stateId are required' }, { status: 400 });
+ }
+ const safeId = /^[A-Za-z0-9._-]{1,128}$/;
+ if (!safeId.test(namespace) || !safeId.test(stateId)) {
+ return NextResponse.json({ error: 'Invalid namespace or stateId format' }, { status: 400 });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!API_KEY) { | ||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'API key not configured' }, { status: 500 }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const body = await request.json(); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!body.fanout_id) { | ||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json({ error: 'fanout_id is required in request body' }, { status: 400 }); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The validation for the request body is not fully robust.
Suggested change
nk-ag marked this conversation as resolved.
Comment on lines
+22
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick Validate fanout_id as UUID v4 Downstream expects a UUID; enforce format early. - if (!body.fanout_id) {
+ const uuidV4 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
+ if (!body.fanout_id || !uuidV4.test(body.fanout_id)) {
return NextResponse.json({ error: 'fanout_id is required in request body' }, { status: 400 });
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const response = await fetch(`${API_BASE_URL}/v0/namespace/${namespace}/state/${stateId}/manual-retry`, { | ||||||||||||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||||||||||||
| headers: { | ||||||||||||||||||||||||||||||||||||||||||
| 'X-API-Key': API_KEY, | ||||||||||||||||||||||||||||||||||||||||||
| 'Content-Type': 'application/json', | ||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify(body), | ||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+26
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a request timeout to the downstream fetch External call has no timeout; a hung downstream will tie up server resources. - const response = await fetch(`${API_BASE_URL}/v0/namespace/${namespace}/state/${stateId}/manual-retry`, {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 10_000);
+ const response = await fetch(`${API_BASE_URL}/v0/namespace/${namespace}/state/${stateId}/manual-retry`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
- body: JSON.stringify(body),
+ body: JSON.stringify(body),
+ signal: controller.signal,
});
+ clearTimeout(timeoutId);📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| if (!response.ok) { | ||||||||||||||||||||||||||||||||||||||||||
| const errorText = await response.text(); | ||||||||||||||||||||||||||||||||||||||||||
| throw new Error(`State manager API error: ${response.status} ${response.statusText} - ${errorText}`); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| const data = await response.json(); | ||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json(data); | ||||||||||||||||||||||||||||||||||||||||||
|
nk-ag marked this conversation as resolved.
|
||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||
| console.error('Error retrying state:', error); | ||||||||||||||||||||||||||||||||||||||||||
| return NextResponse.json( | ||||||||||||||||||||||||||||||||||||||||||
| { error: 'Failed to retry state' }, | ||||||||||||||||||||||||||||||||||||||||||
| { status: 500 } | ||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,133 @@ | ||||||||||||||||||
| 'use client'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import React, { useState, useCallback } from 'react'; | ||||||||||||||||||
| import { useParams, useRouter } from 'next/navigation'; | ||||||||||||||||||
| import { GraphVisualization } from '@/components/GraphVisualization'; | ||||||||||||||||||
| import { GraphTemplateDetail } from '@/components/GraphTemplateDetail'; | ||||||||||||||||||
| import { ThemeToggle } from '@/components/ThemeToggle'; | ||||||||||||||||||
| import { Button } from '@/components/ui/button'; | ||||||||||||||||||
| import { ArrowLeft } from 'lucide-react'; | ||||||||||||||||||
| import { clientApiService } from '@/services/clientApi'; | ||||||||||||||||||
| import { UpsertGraphTemplateResponse } from '@/types/state-manager'; | ||||||||||||||||||
|
|
||||||||||||||||||
| export default function GraphPage() { | ||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||
| const params = useParams(); | ||||||||||||||||||
|
|
||||||||||||||||||
| const namespace = params?.namespace as string; | ||||||||||||||||||
| const runId = params?.runId as string; | ||||||||||||||||||
|
|
||||||||||||||||||
| // Graph template state | ||||||||||||||||||
| const [graphTemplate, setGraphTemplate] = useState<UpsertGraphTemplateResponse | null>(null); | ||||||||||||||||||
| const [isLoadingTemplate, setIsLoadingTemplate] = useState(false); | ||||||||||||||||||
| const [templateError, setTemplateError] = useState<string | null>(null); | ||||||||||||||||||
|
|
||||||||||||||||||
| const handleBack = () => { | ||||||||||||||||||
| // Go back to the previous page or close the tab if opened from external link | ||||||||||||||||||
| if (window.history.length > 1) { | ||||||||||||||||||
| router.back(); | ||||||||||||||||||
| } else { | ||||||||||||||||||
| window.close(); | ||||||||||||||||||
| } | ||||||||||||||||||
| }; | ||||||||||||||||||
|
nk-ag marked this conversation as resolved.
|
||||||||||||||||||
|
|
||||||||||||||||||
| const handleOpenGraphTemplate = useCallback(async (graphName: string) => { | ||||||||||||||||||
| if (!graphName || !namespace) return; | ||||||||||||||||||
|
|
||||||||||||||||||
| try { | ||||||||||||||||||
| setIsLoadingTemplate(true); | ||||||||||||||||||
| setTemplateError(null); | ||||||||||||||||||
| const template = await clientApiService.getGraphTemplate(namespace, graphName); | ||||||||||||||||||
| // Add name and namespace to the template | ||||||||||||||||||
| template.name = graphName; | ||||||||||||||||||
| template.namespace = namespace; | ||||||||||||||||||
| setGraphTemplate(template); | ||||||||||||||||||
|
Comment on lines
+47
to
+49
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mutating the
Suggested change
|
||||||||||||||||||
| } catch (err) { | ||||||||||||||||||
| setTemplateError(err instanceof Error ? err.message : 'Failed to load graph template'); | ||||||||||||||||||
| } finally { | ||||||||||||||||||
| setIsLoadingTemplate(false); | ||||||||||||||||||
| } | ||||||||||||||||||
| }, [namespace]); | ||||||||||||||||||
|
|
||||||||||||||||||
| const handleCloseGraphTemplate = () => { | ||||||||||||||||||
| setGraphTemplate(null); | ||||||||||||||||||
| setTemplateError(null); | ||||||||||||||||||
| }; | ||||||||||||||||||
|
|
||||||||||||||||||
| if (!namespace || !runId) { | ||||||||||||||||||
| return ( | ||||||||||||||||||
| <div className="min-h-screen bg-background flex items-center justify-center"> | ||||||||||||||||||
| <div className="text-center"> | ||||||||||||||||||
| <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto mb-4"></div> | ||||||||||||||||||
| <p className="text-muted-foreground">Loading...</p> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| return ( | ||||||||||||||||||
| <div className="min-h-screen bg-background"> | ||||||||||||||||||
| {/* Header */} | ||||||||||||||||||
| <header className="border-b border-border bg-card/50 backdrop-blur-sm sticky top-0 z-10"> | ||||||||||||||||||
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | ||||||||||||||||||
| <div className="flex items-center justify-between h-16"> | ||||||||||||||||||
| <div className="flex items-center space-x-4"> | ||||||||||||||||||
| <Button | ||||||||||||||||||
| onClick={handleBack} | ||||||||||||||||||
| variant="ghost" | ||||||||||||||||||
| size="sm" | ||||||||||||||||||
| className="flex items-center space-x-2" | ||||||||||||||||||
| > | ||||||||||||||||||
| <ArrowLeft className="w-4 h-4" /> | ||||||||||||||||||
| <span>Back</span> | ||||||||||||||||||
| </Button> | ||||||||||||||||||
| <div className="h-6 w-px bg-border" /> | ||||||||||||||||||
| <div> | ||||||||||||||||||
| <h1 className="text-xl font-semibold text-foreground"> | ||||||||||||||||||
| Graph Visualization | ||||||||||||||||||
| </h1> | ||||||||||||||||||
| <p className="text-sm text-muted-foreground"> | ||||||||||||||||||
| Namespace: {namespace} | Run: {runId} | ||||||||||||||||||
| </p> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| <ThemeToggle /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </header> | ||||||||||||||||||
|
|
||||||||||||||||||
| {/* Main Content */} | ||||||||||||||||||
| <main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | ||||||||||||||||||
| <GraphVisualization | ||||||||||||||||||
| namespace={namespace} | ||||||||||||||||||
| runId={runId} | ||||||||||||||||||
| onGraphTemplateRequest={handleOpenGraphTemplate} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </main> | ||||||||||||||||||
|
|
||||||||||||||||||
| {/* Graph Template Detail Modal - Inline at bottom */} | ||||||||||||||||||
| <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-8"> | ||||||||||||||||||
| {templateError && ( | ||||||||||||||||||
| <div className="mb-4 p-4 bg-destructive/10 border border-destructive/20 rounded-lg"> | ||||||||||||||||||
| <p className="text-sm text-destructive">{templateError}</p> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| )} | ||||||||||||||||||
|
|
||||||||||||||||||
| {isLoadingTemplate && ( | ||||||||||||||||||
| <div className="mb-4 p-4 bg-muted rounded-lg"> | ||||||||||||||||||
| <div className="flex items-center"> | ||||||||||||||||||
| <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary mr-2"></div> | ||||||||||||||||||
| <p className="text-sm text-muted-foreground">Loading graph template...</p> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| )} | ||||||||||||||||||
|
|
||||||||||||||||||
| <GraphTemplateDetail | ||||||||||||||||||
| graphTemplate={graphTemplate} | ||||||||||||||||||
| isOpen={!!graphTemplate} | ||||||||||||||||||
| onClose={handleCloseGraphTemplate} | ||||||||||||||||||
| /> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| </div> | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -79,11 +79,11 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full max-w-6xl mx-auto p-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between mb-6"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2 className="text-2xl font-bold text-gray-800">Graph Template Builder</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h2 className="text-2xl font-bold text-foreground">Graph Template Builder</h2> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {!readOnly && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleSave} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="px-4 py-2 bg-[#031035] text-white rounded-lg hover:bg-[#0a1a4a] transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Save Template | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -93,11 +93,11 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| {/* Nodes Section */} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-8"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between mb-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3 className="text-lg font-semibold text-gray-700">Workflow Nodes</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3 className="text-lg font-semibold text-foreground">Workflow Nodes</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {!readOnly && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={addNode} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="flex items-center space-x-2 px-3 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="flex items-center space-x-2 px-3 py-2 bg-chart-1 text-primary-foreground rounded-md hover:bg-chart-1/90 transition-colors" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <Plus className="w-4 h-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <span>Add Node</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -107,9 +107,9 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="space-y-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {nodes.map((node, index) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={index} className="bg-white border border-gray-200 rounded-lg p-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div key={index} className="bg-card border border-border rounded-lg p-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between mb-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h4 className="text-md font-medium text-gray-800">Node {index + 1}</h4> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <h4 className="text-md font-medium text-card-foreground">Node {index + 1}</h4> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {!readOnly && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <button | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => removeNode(index)} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -122,15 +122,15 @@ export const GraphTemplateBuilder: React.FC<GraphTemplateBuilderProps> = ({ | |||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="grid grid-cols-1 md:grid-cols-2 gap-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <label className="block text-sm font-medium text-gray-700 mb-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <label className="block text-sm font-medium text-card-foreground mb-1"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Node Name | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </label> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type="text" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| value={node.node_name} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={(e) => updateNode(index, { node_name: e.target.value })} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={readOnly} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent disabled:bg-muted bg-input text-foreground" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="Enter node name" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+125
to
135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Label not associated with input (a11y). Wire the label to the input with Apply: - <label className="block text-sm font-medium text-card-foreground mb-1">
+ <label htmlFor={`node_name_${index}`} className="block text-sm font-medium text-card-foreground mb-1">
Node Name
</label>
<input
type="text"
value={node.node_name}
onChange={(e) => updateNode(index, { node_name: e.target.value })}
disabled={readOnly}
- className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent disabled:bg-muted bg-input text-foreground"
+ id={`node_name_${index}`}
+ className="w-full px-3 py-2 border border-input rounded-md focus:outline-none focus:ring-2 focus:ring-ring focus:border-transparent disabled:bg-muted bg-input text-foreground"📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome (2.1.2)[error] 125-127: A form label must be associated with an input. Consider adding a (lint/a11y/noLabelWithoutControl) 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
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.
🛠️ Refactor suggestion
Enforce HTTPS for API_BASE_URL in production
Reduce misconfig/SSRF risk by requiring https when not running locally.
📝 Committable suggestion
🤖 Prompt for AI Agents