diff --git a/faas/web/package-lock.json b/faas/web/package-lock.json index 9949582..6093ba9 100644 --- a/faas/web/package-lock.json +++ b/faas/web/package-lock.json @@ -10,11 +10,13 @@ "dependencies": { "@mantine/code-highlight": "^7.0.0", "@mantine/core": "^7.0.0", + "@mantine/dates": "^7.0.0", "@mantine/form": "^7.0.0", "@mantine/hooks": "^7.0.0", "@mantine/notifications": "^7.0.0", "@tabler/icons-react": "^2.40.0", - "axios": "^1.6.0", + "axios": "^1.11.0", + "dayjs": "^1.11.13", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.8.0" @@ -736,6 +738,22 @@ "react-dom": "^18.x || ^19.x" } }, + "node_modules/@mantine/dates": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.17.8.tgz", + "integrity": "sha512-KYog/YL83PnsMef7EZagpOFq9I2gfnK0eYSzC8YvV9Mb6t/x9InqRssGWVb0GIr+TNILpEkhKoGaSKZNy10Q1g==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.17.8", + "@mantine/hooks": "7.17.8", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, "node_modules/@mantine/form": { "version": "7.17.8", "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.17.8.tgz", @@ -2052,6 +2070,12 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", diff --git a/faas/web/package.json b/faas/web/package.json index 9ea522f..a8370a8 100644 --- a/faas/web/package.json +++ b/faas/web/package.json @@ -3,16 +3,18 @@ "version": "1.0.0", "private": true, "dependencies": { - "@mantine/core": "^7.0.0", - "@mantine/hooks": "^7.0.0", - "@mantine/notifications": "^7.0.0", - "@mantine/form": "^7.0.0", - "@mantine/code-highlight": "^7.0.0", - "@tabler/icons-react": "^2.40.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.8.0", - "axios": "^1.6.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", + "@tabler/icons-react": "^2.40.0", + "axios": "^1.11.0", + "dayjs": "^1.11.13" }, "devDependencies": { "@babel/core": "^7.22.0", @@ -32,6 +34,6 @@ "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", - "dev": "npm start" + "dev": "webpack serve --mode development" } } \ No newline at end of file diff --git a/faas/web/src/App.tsx b/faas/web/src/App.tsx index 11d4376..d48a331 100644 --- a/faas/web/src/App.tsx +++ b/faas/web/src/App.tsx @@ -1,26 +1,60 @@ import React, { useState } from 'react'; -import { MantineProvider, AppShell, Title, Group, Badge, Text } from '@mantine/core'; -import { Notifications } from '@mantine/notifications'; -import { IconFunction } from '@tabler/icons-react'; +import { Box, Title, Tabs, Stack, ActionIcon, Group, Select } from '@mantine/core'; +import { + IconFunction, + IconPlayerPlay, + IconStar, + IconStarFilled +} from '@tabler/icons-react'; import { FunctionList } from './components/FunctionList'; import { FunctionForm } from './components/FunctionForm'; import { ExecutionModal } from './components/ExecutionModal'; import { FunctionDefinition } from './types'; -// Default Mantine theme -const theme: any = { - colorScheme: 'light', -}; - const App: React.FC = () => { + // Determine current route based on pathname + const getCurrentRoute = () => { + const path = window.location.pathname; + if (path.includes('/functions')) return 'functions'; + if (path.includes('/executions')) return 'executions'; + return 'functions'; + }; + + const [currentRoute, setCurrentRoute] = useState(getCurrentRoute()); + const [isFavorited, setIsFavorited] = useState(false); + const [selectedColor, setSelectedColor] = useState(''); const [functionFormOpened, setFunctionFormOpened] = useState(false); const [executionModalOpened, setExecutionModalOpened] = useState(false); const [editingFunction, setEditingFunction] = useState(null); const [executingFunction, setExecutingFunction] = useState(null); const [refreshKey, setRefreshKey] = useState(0); + // Listen for URL changes (for when the shell navigates) + React.useEffect(() => { + const handlePopState = () => { + setCurrentRoute(getCurrentRoute()); + }; + + window.addEventListener('popstate', handlePopState); + return () => window.removeEventListener('popstate', handlePopState); + }, []); + + const handleTabChange = (value: string | null) => { + if (value) { + // Use history.pushState to update URL and notify shell router + const basePath = '/app/faas'; + const newPath = `${basePath}/${value}`; + + // Update the URL and internal state + window.history.pushState(null, '', newPath); + setCurrentRoute(value); + + // Dispatch a custom event so shell can respond if needed + window.dispatchEvent(new PopStateEvent('popstate', { state: null })); + } + }; + const handleCreateFunction = () => { - console.log('handleCreateFunction called'); setEditingFunction(null); setFunctionFormOpened(true); }; @@ -49,49 +83,121 @@ const App: React.FC = () => { setExecutingFunction(null); }; - return ( - - - - - - - - Function as a Service - FaaS - - - Serverless Functions Platform - - - + const toggleFavorite = () => { + setIsFavorited(prev => !prev); + }; - + const colorOptions = [ + { value: 'red', label: 'Red' }, + { value: 'blue', label: 'Blue' }, + { value: 'green', label: 'Green' }, + { value: 'purple', label: 'Purple' }, + { value: 'orange', label: 'Orange' }, + { value: 'pink', label: 'Pink' }, + { value: 'teal', label: 'Teal' }, + ]; + + const renderContent = () => { + switch (currentRoute) { + case 'functions': + return ( - - Executions view coming soon...; + default: + return ( + + ); + } + }; - - - - + return ( + + +
+ +
+ + + Function as a Service + + + {isFavorited ? ( + + ) : ( + + )} + + +
+ + {/* Right-side controls */} + +
+ setSelectedColor(value || '')} + size="sm" + w={150} + /> +
+
+
+
+ + + + } + > + Dashboard + + } + > + Functions + + } + > + Executions + + + + + {renderContent()} + + +
+ + + + +
+ ); +}; + +export default App; diff --git a/faas/web/src/components/ExecutionModal.tsx b/faas/web/src/components/ExecutionModal.tsx index cbc391d..6612b4b 100644 --- a/faas/web/src/components/ExecutionModal.tsx +++ b/faas/web/src/components/ExecutionModal.tsx @@ -18,7 +18,7 @@ import { } from '@mantine/core'; import { IconPlayerPlay, IconPlayerStop, IconRefresh, IconCopy } from '@tabler/icons-react'; import { notifications } from '@mantine/notifications'; -import { functionApi, executionApi } from '../services/api'; +import { functionApi, executionApi } from '../services/apiService'; import { FunctionDefinition, ExecuteFunctionResponse, FunctionExecution } from '../types'; interface ExecutionModalProps { diff --git a/faas/web/src/components/FunctionForm.tsx b/faas/web/src/components/FunctionForm.tsx index 3efbe84..8101b66 100644 --- a/faas/web/src/components/FunctionForm.tsx +++ b/faas/web/src/components/FunctionForm.tsx @@ -15,7 +15,7 @@ import { } from '@mantine/core'; import { useForm } from '@mantine/form'; import { notifications } from '@mantine/notifications'; -import { functionApi, runtimeApi } from '../services/api'; +import { functionApi, runtimeApi } from '../services/apiService'; import { FunctionDefinition, CreateFunctionRequest, UpdateFunctionRequest, RuntimeType } from '../types'; interface FunctionFormProps { diff --git a/faas/web/src/components/FunctionList.tsx b/faas/web/src/components/FunctionList.tsx index 9bdd139..1f5fb56 100644 --- a/faas/web/src/components/FunctionList.tsx +++ b/faas/web/src/components/FunctionList.tsx @@ -26,7 +26,7 @@ import { IconExclamationCircle, } from '@tabler/icons-react'; import { notifications } from '@mantine/notifications'; -import { functionApi } from '../services/api'; +import { functionApi } from '../services/apiService'; import { FunctionDefinition } from '../types'; interface FunctionListProps { diff --git a/faas/web/src/index.tsx b/faas/web/src/index.tsx index d42dfc6..9a73f74 100644 --- a/faas/web/src/index.tsx +++ b/faas/web/src/index.tsx @@ -1,9 +1,21 @@ import React from 'react'; -import { createRoot } from 'react-dom/client'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { MantineProvider } from '@mantine/core'; +import { Notifications } from '@mantine/notifications'; import App from './App'; -const container = document.getElementById('root'); -if (container) { - const root = createRoot(container); - root.render(); -} \ No newline at end of file +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +root.render( + + + + + + + + +); \ No newline at end of file diff --git a/faas/web/src/index.tsx.backup b/faas/web/src/index.tsx.backup new file mode 100644 index 0000000..9a73f74 --- /dev/null +++ b/faas/web/src/index.tsx.backup @@ -0,0 +1,21 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { MantineProvider } from '@mantine/core'; +import { Notifications } from '@mantine/notifications'; +import App from './App'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); + +root.render( + + + + + + + + +); \ No newline at end of file diff --git a/faas/web/src/services/api.ts b/faas/web/src/services/apiService.ts similarity index 100% rename from faas/web/src/services/api.ts rename to faas/web/src/services/apiService.ts diff --git a/faas/web/src/types.ts b/faas/web/src/types.ts new file mode 100644 index 0000000..074f181 --- /dev/null +++ b/faas/web/src/types.ts @@ -0,0 +1,64 @@ +export interface FunctionDefinition { + id: string; + name: string; + description?: string; + runtime: RuntimeType; + code: string; + status: 'active' | 'inactive'; + createdAt: string; + updatedAt: string; + tags?: string[]; + timeout?: number; + memoryLimit?: number; + image?: string; + env_vars?: Record; +} + +export interface FunctionExecution { + id: string; + functionId: string; + input: string; + output?: string; + error?: string; + status: 'pending' | 'running' | 'completed' | 'failed'; + startTime: string; + endTime?: string; + duration?: number; +} + +export type RuntimeType = 'nodejs18' | 'python3.9' | 'go1.20'; + +export interface CreateFunctionRequest { + name: string; + description?: string; + runtime: RuntimeType; + code: string; + image?: string; + timeout?: number; + memory_limit?: number; + env_vars?: Record; + tags?: string[]; +} + +export interface UpdateFunctionRequest extends Partial {} + +export interface ExecuteFunctionRequest { + function_id: string; + input?: any; + async?: boolean; +} + +export interface ExecuteFunctionResponse { + execution_id: string; + output?: any; + error?: string; + status: 'pending' | 'running' | 'completed' | 'failed'; + duration?: number; +} + +export interface RuntimeInfo { + runtime: RuntimeType; + image: string; + version: string; + available: boolean; +} \ No newline at end of file diff --git a/faas/web/webpack.config.js b/faas/web/webpack.config.js index a0f1ae8..f5d10fa 100644 --- a/faas/web/webpack.config.js +++ b/faas/web/webpack.config.js @@ -1,22 +1,16 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const { ModuleFederationPlugin } = require('webpack').container; +const webpack = require('webpack'); module.exports = { mode: 'development', entry: './src/index.tsx', devServer: { port: 3003, - historyApiFallback: true, - static: { - directory: './public', - }, headers: { 'Access-Control-Allow-Origin': '*', }, }, - output: { - publicPath: 'auto', - }, resolve: { extensions: ['.tsx', '.ts', '.js', '.jsx'], }, @@ -46,43 +40,46 @@ module.exports = { name: 'faas', filename: 'remoteEntry.js', exposes: { - './FaaSApp': './src/App', + './App': './src/App.tsx', }, shared: { react: { singleton: true, requiredVersion: '^18.2.0', - eager: true, + eager: false, }, 'react-dom': { singleton: true, requiredVersion: '^18.2.0', - eager: true, + eager: false, }, '@mantine/core': { singleton: true, requiredVersion: '^7.0.0', - eager: true, + eager: false, }, '@mantine/hooks': { singleton: true, requiredVersion: '^7.0.0', - eager: true, + eager: false, }, '@mantine/notifications': { singleton: true, requiredVersion: '^7.0.0', - eager: true, + eager: false, }, '@tabler/icons-react': { singleton: true, requiredVersion: '^2.40.0', - eager: true, + eager: false, }, }, }), new HtmlWebpackPlugin({ template: './public/index.html', }), + new webpack.DefinePlugin({ + 'process.env': JSON.stringify(process.env), + }), ], }; \ No newline at end of file diff --git a/faas/web/webpack.config.js.backup b/faas/web/webpack.config.js.backup new file mode 100644 index 0000000..f5d10fa --- /dev/null +++ b/faas/web/webpack.config.js.backup @@ -0,0 +1,85 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const { ModuleFederationPlugin } = require('webpack').container; +const webpack = require('webpack'); + +module.exports = { + mode: 'development', + entry: './src/index.tsx', + devServer: { + port: 3003, + headers: { + 'Access-Control-Allow-Origin': '*', + }, + }, + resolve: { + extensions: ['.tsx', '.ts', '.js', '.jsx'], + }, + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [ + '@babel/preset-react', + '@babel/preset-typescript', + ], + }, + }, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + ], + }, + plugins: [ + new ModuleFederationPlugin({ + name: 'faas', + filename: 'remoteEntry.js', + exposes: { + './App': './src/App.tsx', + }, + shared: { + react: { + singleton: true, + requiredVersion: '^18.2.0', + eager: false, + }, + 'react-dom': { + singleton: true, + requiredVersion: '^18.2.0', + eager: false, + }, + '@mantine/core': { + singleton: true, + requiredVersion: '^7.0.0', + eager: false, + }, + '@mantine/hooks': { + singleton: true, + requiredVersion: '^7.0.0', + eager: false, + }, + '@mantine/notifications': { + singleton: true, + requiredVersion: '^7.0.0', + eager: false, + }, + '@tabler/icons-react': { + singleton: true, + requiredVersion: '^2.40.0', + eager: false, + }, + }, + }), + new HtmlWebpackPlugin({ + template: './public/index.html', + }), + new webpack.DefinePlugin({ + 'process.env': JSON.stringify(process.env), + }), + ], +}; \ No newline at end of file diff --git a/web/src/components/AppLoader.tsx b/web/src/components/AppLoader.tsx index 5ac1d65..3213bee 100644 --- a/web/src/components/AppLoader.tsx +++ b/web/src/components/AppLoader.tsx @@ -6,7 +6,7 @@ import Breadcrumbs from './Breadcrumbs'; const DemoApp = React.lazy(() => import('demo/App')); const KMSApp = React.lazy(() => import('kms/App')); -const FaaSApp = React.lazy(() => import('faas/FaaSApp')); +const FaaSApp = React.lazy(() => import('faas/App')); const AppLoader: React.FC = () => { const { appName } = useParams<{ appName: string }>();