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/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",

View File

@ -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"
}
}
}

View File

@ -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<FunctionFormProps> = ({
'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(() => {
// Fetch available runtimes from backend
const fetchRuntimes = async () => {
@ -90,7 +164,7 @@ export const FunctionForm: React.FC<FunctionFormProps> = ({
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<FunctionFormProps> = ({
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<FunctionFormProps> = ({
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<FunctionFormProps> = ({
{...form.getInputProps('handler')}
/>
<Textarea
label="Function Code"
rows={16}
resize={"vertical"}
placeholder={`// Node.js example:
exports.handler = async (event, context) => {
return {
statusCode: 200,
body: JSON.stringify({ message: 'Hello World!' })
};
};
// Python example:
def handler(event, context):
return {
'statusCode': 200,
'body': json.dumps({'message': 'Hello World!'})
}`}
minRows={16}
{...form.getInputProps('code')}
/>
<Box>
<Text size="sm" fw={500} mb={5}>
Function Code
</Text>
<Box
style={{
border: '1px solid #dee2e6',
borderRadius: '4px',
overflow: 'hidden'
}}
>
<Editor
height="400px"
language={getEditorLanguage(form.values.runtime)}
value={form.values.code}
onChange={(value) => form.setFieldValue('code', value || '')}
options={{
minimap: { enabled: false },
scrollBeyondLastLine: false,
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
label="Environment Variables"