code editor

This commit is contained in:
2025-08-31 10:52:10 -04:00
parent d05af3b385
commit 61bed7b412
3 changed files with 175 additions and 32 deletions

View File

@ -14,9 +14,11 @@
"@mantine/form": "^7.0.0", "@mantine/form": "^7.0.0",
"@mantine/hooks": "^7.0.0", "@mantine/hooks": "^7.0.0",
"@mantine/notifications": "^7.0.0", "@mantine/notifications": "^7.0.0",
"@monaco-editor/react": "^4.7.0",
"@tabler/icons-react": "^2.40.0", "@tabler/icons-react": "^2.40.0",
"axios": "^1.11.0", "axios": "^1.11.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"monaco-editor": "^0.52.2",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.8.0" "react-router-dom": "^6.8.0"
@ -801,6 +803,29 @@
"react": "^18.x || ^19.x" "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": { "node_modules/@remix-run/router": {
"version": "1.23.0", "version": "1.23.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz",
@ -3837,6 +3862,12 @@
"node": "*" "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": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -5240,6 +5271,12 @@
"wbuf": "^1.7.3" "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": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@ -3,18 +3,20 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"react": "^18.2.0", "@mantine/code-highlight": "^7.0.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.8.0",
"@mantine/core": "^7.0.0", "@mantine/core": "^7.0.0",
"@mantine/hooks": "^7.0.0",
"@mantine/notifications": "^7.0.0",
"@mantine/dates": "^7.0.0", "@mantine/dates": "^7.0.0",
"@mantine/form": "^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", "@tabler/icons-react": "^2.40.0",
"axios": "^1.11.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": { "devDependencies": {
"@babel/core": "^7.22.0", "@babel/core": "^7.22.0",

View File

@ -4,7 +4,6 @@ import {
TextInput, TextInput,
Select, Select,
NumberInput, NumberInput,
Textarea,
Button, Button,
Group, Group,
Stack, Stack,
@ -12,9 +11,11 @@ import {
Paper, Paper,
Divider, Divider,
JsonInput, JsonInput,
Box,
} from '@mantine/core'; } from '@mantine/core';
import { useForm } from '@mantine/form'; import { useForm } from '@mantine/form';
import { notifications } from '@mantine/notifications'; import { notifications } from '@mantine/notifications';
import Editor from '@monaco-editor/react';
import { functionApi, runtimeApi } from '../services/apiService'; import { functionApi, runtimeApi } from '../services/apiService';
import { FunctionDefinition, CreateFunctionRequest, UpdateFunctionRequest, RuntimeType } from '../types'; import { FunctionDefinition, CreateFunctionRequest, UpdateFunctionRequest, RuntimeType } from '../types';
@ -41,6 +42,79 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
'go1.20': 'golang:1.20-alpine', 'go1.20': 'golang:1.20-alpine',
}; };
// Map runtime to Monaco editor language
const getEditorLanguage = (runtime: string): string => {
const languageMap: Record<string, string> = {
'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<string, string> = {
'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(() => { useEffect(() => {
// Fetch available runtimes from backend // Fetch available runtimes from backend
const fetchRuntimes = async () => { const fetchRuntimes = async () => {
@ -90,7 +164,7 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
runtime: 'nodejs18' as RuntimeType, runtime: 'nodejs18' as RuntimeType,
image: DEFAULT_IMAGES['nodejs18'] || '', image: DEFAULT_IMAGES['nodejs18'] || '',
handler: 'index.handler', handler: 'index.handler',
code: '', code: getDefaultCode('nodejs18'),
environment: '{}', environment: '{}',
timeout: '30s', timeout: '30s',
memory: 128, memory: 128,
@ -110,7 +184,7 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
runtime: 'nodejs18' as RuntimeType, runtime: 'nodejs18' as RuntimeType,
image: DEFAULT_IMAGES['nodejs18'] || '', image: DEFAULT_IMAGES['nodejs18'] || '',
handler: 'index.handler', handler: 'index.handler',
code: '', code: getDefaultCode('nodejs18'),
environment: '{}', environment: '{}',
timeout: '30s', timeout: '30s',
memory: 128, memory: 128,
@ -136,6 +210,11 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
form.setFieldValue('image', DEFAULT_IMAGES[runtime]); form.setFieldValue('image', DEFAULT_IMAGES[runtime]);
} }
form.setFieldValue('runtime', runtime as RuntimeType); 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) => { const handleSubmit = async (values: typeof form.values) => {
@ -288,27 +367,52 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
{...form.getInputProps('handler')} {...form.getInputProps('handler')}
/> />
<Textarea <Box>
label="Function Code" <Text size="sm" fw={500} mb={5}>
rows={16} Function Code
resize={"vertical"} </Text>
placeholder={`// Node.js example: <Box
exports.handler = async (event, context) => { style={{
return { border: '1px solid #dee2e6',
statusCode: 200, borderRadius: '4px',
body: JSON.stringify({ message: 'Hello World!' }) overflow: 'hidden'
}; }}
}; >
<Editor
// Python example: height="400px"
def handler(event, context): language={getEditorLanguage(form.values.runtime)}
return { value={form.values.code}
'statusCode': 200, onChange={(value) => form.setFieldValue('code', value || '')}
'body': json.dumps({'message': 'Hello World!'}) options={{
}`} minimap: { enabled: false },
minRows={16} scrollBeyondLastLine: false,
{...form.getInputProps('code')} fontSize: 14,
/> lineNumbers: 'on',
roundedSelection: false,
scrollbar: {
vertical: 'visible',
horizontal: 'visible'
},
automaticLayout: true,
wordWrap: 'on',
tabSize: 2,
insertSpaces: true,
folding: true,
lineDecorationsWidth: 0,
lineNumbersMinChars: 3,
renderLineHighlight: 'line',
selectOnLineNumbers: true,
theme: 'vs-light'
}}
loading={<Text ta="center" p="xl">Loading editor...</Text>}
/>
</Box>
{form.errors.code && (
<Text size="xs" c="red" mt={5}>
{form.errors.code}
</Text>
)}
</Box>
<JsonInput <JsonInput
label="Environment Variables" label="Environment Variables"