// File: components/workflows/dsl-editor.tsx // Change Log: 2026-05-03 — แก้ CSP violation โดยเปลี่ยนจาก CDN เป็น self-hosted Monaco assets 'use client'; import { useState, useRef, useEffect } from 'react'; import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { CheckCircle, AlertCircle, Play, Loader2 } from 'lucide-react'; import Editor, { OnMount, loader } from '@monaco-editor/react'; import { workflowApi } from '@/lib/api/workflows'; import { ValidationResult } from '@/types/workflow'; import { useTheme } from 'next-themes'; // กำหนดให้ Monaco โหลด assets จาก self-hosted path แทน cdn.jsdelivr.net // เพื่อผ่าน CSP directive: style-src 'self' 'unsafe-inline' loader.config({ paths: { vs: '/monaco-vs' } }); interface DSLEditorProps { initialValue?: string; onChange?: (value: string) => void; readOnly?: boolean; // FR-025: callback เมื่อผล validate เปลี่ยน — parent ใช้ disable Save button onValidationChange?: (hasErrors: boolean) => void; } export function DSLEditor({ initialValue = '', onChange, readOnly = false, onValidationChange }: DSLEditorProps) { const [dsl, setDsl] = useState(initialValue); const [validationResult, setValidationResult] = useState(null); const [isValidating, setIsValidating] = useState(false); const editorRef = useRef(null); const { theme } = useTheme(); // Update internal state if initialValue changes (e.g. loaded from API) useEffect(() => { setDsl(initialValue); }, [initialValue]); const handleEditorChange = (value: string | undefined) => { const newValue = value || ''; setDsl(newValue); onChange?.(newValue); // Clear previous validation result on edit to avoid stale state if (validationResult) { setValidationResult(null); } }; const handleEditorDidMount: OnMount = (editor) => { editorRef.current = editor; }; const validateDSL = async () => { setIsValidating(true); try { const result = await workflowApi.validateDSL(dsl); setValidationResult(result); // FR-025: แจ้ง parent ว่ามี validation errors หรือไม่ onValidationChange?.(!result.valid); } catch (_error) { // Validation failed - error state shown in UI setValidationResult({ valid: false, errors: ['Validation failed due to server error'] }); onValidationChange?.(true); } finally { setIsValidating(false); } }; interface TestResult { success: boolean; message: string; } const [testResult, setTestResult] = useState(null); const [isTesting, setIsTesting] = useState(false); const testWorkflow = async () => { setIsTesting(true); setTestResult(null); try { // Mock test execution await new Promise((resolve) => setTimeout(resolve, 1000)); setTestResult({ success: true, message: 'Workflow simulation completed successfully.' }); } catch { setTestResult({ success: false, message: 'Workflow simulation failed.' }); } finally { setIsTesting(false); } }; return (

Workflow DSL

{validationResult && ( {validationResult.valid ? : } {validationResult.valid ? ( DSL is valid and ready to deploy. ) : (

Validation Errors:

    {validationResult.errors?.map((error: string, i: number) => (
  • {error}
  • ))}
)}
)} {testResult && ( {testResult.success ? : } {testResult.message} )}
); }