256 lines
6.8 KiB
TypeScript
256 lines
6.8 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import {
|
|
Paper,
|
|
Stack,
|
|
Text,
|
|
Divider,
|
|
Box,
|
|
ScrollArea,
|
|
} from '@mantine/core';
|
|
import {
|
|
FormSidebar,
|
|
FormField
|
|
} from '@skybridge/web-components';
|
|
import Editor from '@monaco-editor/react';
|
|
import { functionApi, runtimeApi } from '../services/apiService';
|
|
import { FunctionDefinition, CreateFunctionRequest, UpdateFunctionRequest, RuntimeType } from '../types';
|
|
|
|
interface FunctionSidebarProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
editFunction?: FunctionDefinition;
|
|
}
|
|
|
|
export const FunctionSidebar: React.FC<FunctionSidebarProps> = ({
|
|
opened,
|
|
onClose,
|
|
onSuccess,
|
|
editFunction,
|
|
}) => {
|
|
const [runtimeOptions, setRuntimeOptions] = useState<Array<{value: string; label: string}>>([]);
|
|
const [codeContent, setCodeContent] = useState('');
|
|
|
|
// Default images for each runtime
|
|
const DEFAULT_IMAGES: Record<string, string> = {
|
|
'nodejs18': 'node:18-alpine',
|
|
'python3.9': 'python:3.9-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"
|
|
"time"
|
|
)
|
|
|
|
type Event map[string]interface{}
|
|
|
|
func Handler(ctx context.Context, event Event) (map[string]interface{}, error) {
|
|
fmt.Printf("Event: %+v\\n", event)
|
|
|
|
return map[string]interface{}{
|
|
"statusCode": 200,
|
|
"body": map[string]interface{}{
|
|
"message": "Hello from Go!",
|
|
"timestamp": time.Now().Format(time.RFC3339),
|
|
},
|
|
}, nil
|
|
}`
|
|
};
|
|
return templates[runtime] || templates['nodejs18'];
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadRuntimeOptions();
|
|
if (editFunction) {
|
|
setCodeContent(editFunction.code || '');
|
|
}
|
|
}, [editFunction]);
|
|
|
|
const loadRuntimeOptions = async () => {
|
|
try {
|
|
const runtimes = await runtimeApi.listRuntimes();
|
|
const options = runtimes.map((runtime: RuntimeType) => ({
|
|
value: runtime.name,
|
|
label: `${runtime.name} (${runtime.version})`
|
|
}));
|
|
setRuntimeOptions(options);
|
|
} catch (error) {
|
|
console.error('Failed to load runtimes:', error);
|
|
// Fallback options
|
|
setRuntimeOptions([
|
|
{ value: 'nodejs18', label: 'Node.js 18' },
|
|
{ value: 'python3.9', label: 'Python 3.9' },
|
|
{ value: 'go1.20', label: 'Go 1.20' }
|
|
]);
|
|
}
|
|
};
|
|
|
|
const fields: FormField[] = [
|
|
{
|
|
name: 'name',
|
|
label: 'Function Name',
|
|
type: 'text',
|
|
required: true,
|
|
placeholder: 'my-function',
|
|
validation: { pattern: /^[a-z0-9-]+$/ },
|
|
},
|
|
{
|
|
name: 'description',
|
|
label: 'Description',
|
|
type: 'textarea',
|
|
required: false,
|
|
placeholder: 'Function description...',
|
|
},
|
|
{
|
|
name: 'runtime',
|
|
label: 'Runtime',
|
|
type: 'select',
|
|
required: true,
|
|
options: runtimeOptions,
|
|
defaultValue: 'nodejs18',
|
|
},
|
|
{
|
|
name: 'timeout',
|
|
label: 'Timeout (seconds)',
|
|
type: 'number',
|
|
required: true,
|
|
defaultValue: 30,
|
|
},
|
|
{
|
|
name: 'memory',
|
|
label: 'Memory (MB)',
|
|
type: 'number',
|
|
required: true,
|
|
defaultValue: 128,
|
|
},
|
|
{
|
|
name: 'environment_variables',
|
|
label: 'Environment Variables',
|
|
type: 'json',
|
|
required: false,
|
|
defaultValue: '{}',
|
|
},
|
|
];
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
const submitData = {
|
|
...values,
|
|
code: codeContent,
|
|
docker_image: DEFAULT_IMAGES[values.runtime] || DEFAULT_IMAGES['nodejs18'],
|
|
};
|
|
|
|
if (editFunction) {
|
|
const updateRequest: UpdateFunctionRequest = {
|
|
description: submitData.description,
|
|
code: submitData.code,
|
|
timeout: submitData.timeout,
|
|
memory: submitData.memory,
|
|
environment_variables: submitData.environment_variables,
|
|
docker_image: submitData.docker_image,
|
|
};
|
|
await functionApi.updateFunction(editFunction.id, updateRequest);
|
|
} else {
|
|
const createRequest: CreateFunctionRequest = submitData;
|
|
await functionApi.createFunction(createRequest);
|
|
}
|
|
};
|
|
|
|
// Create a custom sidebar that includes the Monaco editor
|
|
return (
|
|
<Paper
|
|
style={{
|
|
position: 'fixed',
|
|
top: 60,
|
|
right: opened ? 0 : '-600px',
|
|
bottom: 0,
|
|
width: '600px',
|
|
zIndex: 1000,
|
|
borderRadius: 0,
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
borderLeft: '1px solid var(--mantine-color-gray-3)',
|
|
backgroundColor: 'var(--mantine-color-body)',
|
|
transition: 'right 0.3s ease',
|
|
}}
|
|
>
|
|
<ScrollArea style={{ flex: 1 }}>
|
|
<Stack gap="md" p="md">
|
|
<FormSidebar
|
|
opened={true} // Always open since we're embedding it
|
|
onClose={() => {}} // Handled by parent
|
|
onSuccess={onSuccess}
|
|
title="Function"
|
|
editMode={!!editFunction}
|
|
editItem={editFunction}
|
|
fields={fields}
|
|
onSubmit={handleSubmit}
|
|
width={600}
|
|
style={{ position: 'relative', right: 'auto', top: 'auto', bottom: 'auto' }}
|
|
/>
|
|
|
|
<Divider />
|
|
|
|
<Box>
|
|
<Text fw={500} mb="sm">Code Editor</Text>
|
|
<Box h={300} style={{ border: '1px solid var(--mantine-color-gray-3)' }}>
|
|
<Editor
|
|
height="300px"
|
|
language={getEditorLanguage(editFunction?.runtime || 'nodejs18')}
|
|
value={codeContent}
|
|
onChange={(value) => setCodeContent(value || '')}
|
|
theme="vs-dark"
|
|
options={{
|
|
minimap: { enabled: false },
|
|
scrollBeyondLastLine: false,
|
|
fontSize: 14,
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Box>
|
|
</Stack>
|
|
</ScrollArea>
|
|
</Paper>
|
|
);
|
|
}; |