From 61bed7b41244da38716b314eea69a3fdbc0e35e1 Mon Sep 17 00:00:00 2001 From: Ryan Copley Date: Sun, 31 Aug 2025 10:52:10 -0400 Subject: [PATCH] code editor --- faas/web/package-lock.json | 37 ++++++ faas/web/package.json | 18 +-- faas/web/src/components/FunctionForm.tsx | 152 +++++++++++++++++++---- 3 files changed, 175 insertions(+), 32 deletions(-) diff --git a/faas/web/package-lock.json b/faas/web/package-lock.json index 6093ba9..19520a2 100644 --- a/faas/web/package-lock.json +++ b/faas/web/package-lock.json @@ -14,9 +14,11 @@ "@mantine/form": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", + "@monaco-editor/react": "^4.7.0", "@tabler/icons-react": "^2.40.0", "axios": "^1.11.0", "dayjs": "^1.11.13", + "monaco-editor": "^0.52.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.8.0" @@ -801,6 +803,29 @@ "react": "^18.x || ^19.x" } }, + "node_modules/@monaco-editor/loader": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", + "integrity": "sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, + "node_modules/@monaco-editor/react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", + "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.5.0" + }, + "peerDependencies": { + "monaco-editor": ">= 0.25.0 < 1", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@remix-run/router": { "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", @@ -3837,6 +3862,12 @@ "node": "*" } }, + "node_modules/monaco-editor": { + "version": "0.52.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.2.tgz", + "integrity": "sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5240,6 +5271,12 @@ "wbuf": "^1.7.3" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", diff --git a/faas/web/package.json b/faas/web/package.json index a8370a8..1e793a2 100644 --- a/faas/web/package.json +++ b/faas/web/package.json @@ -3,18 +3,20 @@ "version": "1.0.0", "private": true, "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.8.0", + "@mantine/code-highlight": "^7.0.0", "@mantine/core": "^7.0.0", - "@mantine/hooks": "^7.0.0", - "@mantine/notifications": "^7.0.0", "@mantine/dates": "^7.0.0", "@mantine/form": "^7.0.0", - "@mantine/code-highlight": "^7.0.0", + "@mantine/hooks": "^7.0.0", + "@mantine/notifications": "^7.0.0", + "@monaco-editor/react": "^4.7.0", "@tabler/icons-react": "^2.40.0", "axios": "^1.11.0", - "dayjs": "^1.11.13" + "dayjs": "^1.11.13", + "monaco-editor": "^0.52.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.8.0" }, "devDependencies": { "@babel/core": "^7.22.0", @@ -36,4 +38,4 @@ "build": "webpack --mode production", "dev": "webpack serve --mode development" } -} \ No newline at end of file +} diff --git a/faas/web/src/components/FunctionForm.tsx b/faas/web/src/components/FunctionForm.tsx index 8101b66..db2dc43 100644 --- a/faas/web/src/components/FunctionForm.tsx +++ b/faas/web/src/components/FunctionForm.tsx @@ -4,7 +4,6 @@ import { TextInput, Select, NumberInput, - Textarea, Button, Group, Stack, @@ -12,9 +11,11 @@ import { Paper, Divider, JsonInput, + Box, } from '@mantine/core'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; +import Editor from '@monaco-editor/react'; import { functionApi, runtimeApi } from '../services/apiService'; import { FunctionDefinition, CreateFunctionRequest, UpdateFunctionRequest, RuntimeType } from '../types'; @@ -41,6 +42,79 @@ export const FunctionForm: React.FC = ({ 'go1.20': 'golang:1.20-alpine', }; + // Map runtime to Monaco editor language + const getEditorLanguage = (runtime: string): string => { + const languageMap: Record = { + 'nodejs18': 'javascript', + 'python3.9': 'python', + 'go1.20': 'go', + }; + return languageMap[runtime] || 'javascript'; + }; + + // Get default code template based on runtime + const getDefaultCode = (runtime: string): string => { + const templates: Record = { + 'nodejs18': `exports.handler = async (event, context) => { + console.log('Event:', JSON.stringify(event, null, 2)); + + return { + statusCode: 200, + body: JSON.stringify({ + message: 'Hello from Node.js!', + timestamp: new Date().toISOString() + }) + }; +};`, + 'python3.9': `import json +from datetime import datetime + +def handler(event, context): + print('Event:', json.dumps(event, indent=2)) + + return { + 'statusCode': 200, + 'body': json.dumps({ + 'message': 'Hello from Python!', + 'timestamp': datetime.now().isoformat() + }) + }`, + 'go1.20': `package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" +) + +type Event map[string]interface{} +type Response struct { + StatusCode int \`json:"statusCode"\` + Body string \`json:"body"\` +} + +func Handler(ctx context.Context, event Event) (Response, error) { + eventJSON, _ := json.MarshalIndent(event, "", " ") + log.Printf("Event: %s", eventJSON) + + body := map[string]interface{}{ + "message": "Hello from Go!", + "timestamp": time.Now().Format(time.RFC3339), + } + + bodyJSON, _ := json.Marshal(body) + + return Response{ + StatusCode: 200, + Body: string(bodyJSON), + }, nil +}` + }; + return templates[runtime] || templates['nodejs18']; + }; + useEffect(() => { // Fetch available runtimes from backend const fetchRuntimes = async () => { @@ -90,7 +164,7 @@ export const FunctionForm: React.FC = ({ runtime: 'nodejs18' as RuntimeType, image: DEFAULT_IMAGES['nodejs18'] || '', handler: 'index.handler', - code: '', + code: getDefaultCode('nodejs18'), environment: '{}', timeout: '30s', memory: 128, @@ -110,7 +184,7 @@ export const FunctionForm: React.FC = ({ runtime: 'nodejs18' as RuntimeType, image: DEFAULT_IMAGES['nodejs18'] || '', handler: 'index.handler', - code: '', + code: getDefaultCode('nodejs18'), environment: '{}', timeout: '30s', memory: 128, @@ -136,6 +210,11 @@ export const FunctionForm: React.FC = ({ form.setFieldValue('image', DEFAULT_IMAGES[runtime]); } form.setFieldValue('runtime', runtime as RuntimeType); + + // If creating a new function and no code is set, provide default template + if (!isEditing && runtime && (!form.values.code || form.values.code.trim() === '')) { + form.setFieldValue('code', getDefaultCode(runtime)); + } }; const handleSubmit = async (values: typeof form.values) => { @@ -288,27 +367,52 @@ export const FunctionForm: React.FC = ({ {...form.getInputProps('handler')} /> -