lblt 1 ماه پیش
والد
کامیت
39f191389d

+ 2 - 0
.env.local

@@ -0,0 +1,2 @@
+# Local development environment variables
+VITE_API_URL=http://localhost:8080

+ 30 - 26
src/App.tsx

@@ -22,6 +22,7 @@ import { BrowserRouter, Outlet, Route, Routes } from "react-router";
 import { authProvider } from "./authProvider";
 import { Header } from "./components/header";
 import { ColorModeContextProvider } from "./contexts/color-mode";
+import { API_URL } from "./config";
 
 // Import our custom pages
 import {
@@ -30,15 +31,15 @@ import {
   DeploymentShow,
 } from "./pages/deployments";
 import {
-  TemplateList,
-  TemplateCreate,
-  TemplateShow,
-} from "./pages/templates";
+  BlueprintList,
+  BlueprintCreate,
+  BlueprintShow,
+} from "./pages/blueprints";
 import {
-  AppList,
-  AppCreate,
-  AppShow,
-} from "./pages/apps";
+  ComponentList,
+  ComponentCreate,
+  ComponentShow,
+} from "./pages/components";
 import {
   ClientCreate,
   ClientList,
@@ -59,6 +60,8 @@ import { Register } from "./pages/register";
 import { DashboardOutlined, AppstoreOutlined } from "@ant-design/icons";
 
 function App() {
+  const apiUrl = API_URL || "http://localhost:8080/api/v1";
+
   return (
     <BrowserRouter>
       <RefineKbarProvider>
@@ -66,7 +69,7 @@ function App() {
           <AntdApp>
             <DevtoolsProvider>
               <Refine
-                dataProvider={dataProvider("http://localhost:8080/api/v1")}
+                dataProvider={dataProvider(apiUrl)}
                 notificationProvider={useNotificationProvider}
                 routerProvider={routerBindings}
                 authProvider={authProvider}
@@ -91,24 +94,25 @@ function App() {
                     },
                   },
                   {
-                    name: "templates",
-                    list: "/templates",
-                    create: "/templates/create",
+                    name: "blueprints",
+                    list: "/blueprints",
+                    create: "/blueprints/create",
+                    show: "/blueprints/show/:id",
                     meta: {
                       canDelete: true,
                       icon: <span className="anticon">📄</span>,
-                      label: "Templates",
+                      label: "Blueprints",
                     },
                   },
                   {
-                    name: "apps",
-                    list: "/apps",
-                    create: "/apps/create",
-                    show: "/apps/show/:id",
+                    name: "components",
+                    list: "/components",
+                    create: "/components/create",
+                    show: "/components/show/:id",
                     meta: {
                       canDelete: true,
                       icon: <AppstoreOutlined />,
-                      label: "Apps",
+                      label: "Components",
                     },
                   },
                   {
@@ -178,15 +182,15 @@ function App() {
                       <Route path="create" element={<DeploymentCreate />} />
                       <Route path="show/:id" element={<DeploymentShow />} />
                     </Route>
-                    <Route path="/templates">
-                      <Route index element={<TemplateList />} />
-                      <Route path="create" element={<TemplateCreate />} />
-                      <Route path="show/:id" element={<TemplateShow />} />
+                    <Route path="/blueprints">
+                      <Route index element={<BlueprintList />} />
+                      <Route path="create" element={<BlueprintCreate />} />
+                      <Route path="show/:id" element={<BlueprintShow />} />
                     </Route>
-                    <Route path="/apps">
-                      <Route index element={<AppList />} />
-                      <Route path="create" element={<AppCreate />} />
-                      <Route path="show/:id" element={<AppShow />} />
+                    <Route path="/components">
+                      <Route index element={<ComponentList />} />
+                      <Route path="create" element={<ComponentCreate />} />
+                      <Route path="show/:id" element={<ComponentShow />} />
                     </Route>
                     <Route path="/clients">
                       <Route index element={<ClientList />} />

+ 10 - 9
src/authProvider.ts

@@ -1,13 +1,11 @@
 import type { AuthProvider } from "@refinedev/core";
-import axios from "axios";
+import axios, { AxiosError } from "axios";
+import { API_URL } from "./config";
 
 export const TOKEN_KEY = "byop-auth";
 export const REFRESH_TOKEN_KEY = "byop-refresh";
 export const USER_KEY = "byop-user";
 
-// API base URL
-const API_URL = "http://localhost:8080/api/v1";
-
 export const authProvider: AuthProvider = {
   login: async ({ email, password }) => {
     if (!email || !password) {
@@ -54,8 +52,9 @@ export const authProvider: AuthProvider = {
         },
       };
     } catch (error) {
+      const axiosError = error as AxiosError;
       const errorMessage = 
-        error.response?.data?.message || 
+        (axiosError.response?.data as { message?: string })?.message || 
         "Something went wrong. Please try again.";
         
       return {
@@ -105,9 +104,10 @@ export const authProvider: AuthProvider = {
         },
       };
     } catch (error) {
+      const axiosError = error as AxiosError;
       const errorMessage = 
-        error.response?.data?.message || 
-        "Something went wrong. Please try again.";
+      (axiosError.response?.data as { message?: string })?.message || 
+      "Something went wrong. Please try again.";
       
       return {
         success: false,
@@ -138,9 +138,10 @@ export const authProvider: AuthProvider = {
         success: true,
       };
     } catch (error) {
+      const axiosError = error as AxiosError;
       const errorMessage = 
-        error.response?.data?.message || 
-        "Something went wrong. Please try again.";
+      (axiosError.response?.data as { message?: string })?.message || 
+      "Something went wrong. Please try again.";
       
       return {
         success: false,

+ 14 - 0
src/config.ts

@@ -0,0 +1,14 @@
+// Configuration settings for the application
+// This centralizes all environment-related configuration
+
+// API URL with proper path handling
+export const API_URL = (() => {
+  const url = import.meta.env.VITE_API_URL || 'http://localhost:8080';
+  // Add /api/v1 if not already included in the URL
+  if (!url.endsWith('/api/v1')) {
+    return `${url}/api/v1`;
+  }
+  return url;
+})();
+
+// Other configuration settings can be added here

+ 0 - 163
src/pages/apps/create.tsx

@@ -1,163 +0,0 @@
-import { Create, useForm } from "@refinedev/antd";
-import { Form, Input, Select, Tabs } from "antd";
-import { useState } from "react";
-import { AppFormData, AppType } from "../../types/app";
-
-const { TabPane } = Tabs;
-
-export const AppCreate = () => {
-  const { formProps, saveButtonProps } = useForm<AppFormData>({});
-  const [appType, setAppType] = useState<AppType>("frontend");
-
-  const handleTypeChange = (value: AppType) => {
-    setAppType(value);
-  };
-
-  return (
-    <Create saveButtonProps={saveButtonProps}>
-      <Form {...formProps} layout="vertical">
-        <Form.Item
-          label={"App Name"}
-          name="name"
-          rules={[{ required: true }]}
-        >
-          <Input />
-        </Form.Item>
-
-        <Form.Item
-          label={"Description"}
-          name="description"
-          rules={[{ required: true }]}
-        >
-          <Input.TextArea rows={3} />
-        </Form.Item>
-
-        <Form.Item
-          label={"App Type"}
-          name="type"
-          initialValue="frontend"
-          rules={[{ required: true }]}
-        >
-          <Select
-            options={[
-              { value: "frontend", label: "Frontend Application" },
-              { value: "backend", label: "Backend Service" },
-              { value: "api", label: "API Service" },
-              { value: "database", label: "Database" },
-              { value: "microservice", label: "Microservice" },
-            ]}
-            onChange={handleTypeChange}
-          />
-        </Form.Item>
-
-        <Form.Item
-          label={"Language/Framework"}
-          name="language"
-          rules={[{ required: true }]}
-        >
-          <Select
-            options={
-              appType === "frontend" 
-                ? [
-                  { value: "reactjs", label: "React JS" },
-                  { value: "vuejs", label: "Vue JS" },
-                  { value: "angular", label: "Angular" },
-                  { value: "nextjs", label: "Next.js" },
-                ] 
-                : appType === "database"
-                ? [
-                  { value: "postgresql", label: "PostgreSQL" },
-                  { value: "mysql", label: "MySQL" },
-                  { value: "mongodb", label: "MongoDB" },
-                  { value: "redis", label: "Redis" },
-                ]
-                : [
-                  { value: "nodejs", label: "Node.js" },
-                  { value: "python", label: "Python" },
-                  { value: "java", label: "Java" },
-                  { value: "golang", label: "Go" },
-                  { value: "dotnet", label: ".NET Core" },
-                ]
-            }
-          />
-        </Form.Item>
-
-        <Form.Item
-          label={"Version"}
-          name="version"
-          initialValue={"1.0.0"}
-          rules={[{ required: true }]}
-        >
-          <Input />
-        </Form.Item>
-
-        <Tabs defaultActiveKey="configuration">
-          <TabPane tab="Configuration" key="configuration">
-            <Form.Item
-              label={"Configuration File"}
-              name="configFile"
-              rules={[{ required: true }]}
-            >
-              <Input.TextArea rows={10} placeholder={
-                appType === "frontend" 
-                  ? `{\n  "name": "frontend-app",\n  "port": 3000,\n  "environment": "production",\n  "resources": {\n    "cpu": "0.5",\n    "memory": "512Mi"\n  }\n}`
-                  : appType === "backend" || appType === "api"
-                  ? `{\n  "name": "api-service",\n  "port": 8080,\n  "environment": "production",\n  "resources": {\n    "cpu": "1",\n    "memory": "1Gi"\n  },\n  "scaling": {\n    "min": 2,\n    "max": 5\n  }\n}`
-                  : `{\n  "name": "database",\n  "type": "postgres",\n  "version": "13",\n  "storage": "10Gi",\n  "backups": true\n}`
-              } />
-            </Form.Item>
-          </TabPane>
-
-          <TabPane tab="Environment Variables" key="env-vars">
-            <Form.Item
-              label={"Environment Variables"}
-              name="envVariables"
-            >
-              <Input.TextArea rows={5} placeholder={`PORT=8080\nNODE_ENV=production\nDATABASE_URL=postgresql://user:password@localhost:5432/db\nAPI_KEY=your-api-key`} />
-            </Form.Item>
-          </TabPane>
-
-          <TabPane tab="Source Code" key="source-code">
-            <Form.Item
-              label={"Source Code Repository"}
-              name="repository"
-            >
-              <Input placeholder="https://github.com/username/repo" />
-            </Form.Item>
-            
-            <Form.Item
-              label={"Branch"}
-              name="branch"
-              initialValue={"main"}
-            >
-              <Input />
-            </Form.Item>
-
-            <Form.Item
-              label={"Build Command"}
-              name="buildCommand"
-            >
-              <Input placeholder="npm install && npm run build" />
-            </Form.Item>
-          </TabPane>
-
-          <TabPane tab="Advanced Settings" key="advanced">
-            <Form.Item
-              label={"Resource Requirements"}
-              name="resources"
-            >
-              <Input.TextArea rows={4} placeholder={`CPU: 0.5\nMemory: 512Mi\nStorage: 1Gi`} />
-            </Form.Item>
-            
-            <Form.Item
-              label={"Scale Settings"}
-              name="scaleSettings"
-            >
-              <Input.TextArea rows={3} placeholder={`Min Instances: 2\nMax Instances: 5\nCPU Threshold: 75%`} />
-            </Form.Item>
-          </TabPane>
-        </Tabs>
-      </Form>
-    </Create>
-  );
-}

+ 0 - 171
src/pages/apps/show.tsx

@@ -1,171 +0,0 @@
-import { Show, TextField, TagField } from "@refinedev/antd";
-import { useShow } from "@refinedev/core";
-import { Typography, Card, Descriptions, Tabs, Badge, Divider, Button, Row, Col, Tag, Alert } from "antd";
-import { CodeOutlined, BranchesOutlined, ApiOutlined, DatabaseOutlined } from '@ant-design/icons';
-
-const { Title, Text } = Typography;
-const { TabPane } = Tabs;
-
-export const AppShow = () => {
-  const { queryResult } = useShow({});
-  const { data, isLoading } = queryResult;
-  const record = data?.data;
-
-  // Function to get an icon based on app type
-  const getAppTypeIcon = (type: string) => {
-    switch (type) {
-      case 'frontend':
-        return <span role="img" aria-label="frontend">🖥️</span>;
-      case 'backend':
-        return <span role="img" aria-label="backend">⚙️</span>;
-      case 'api':
-        return <ApiOutlined />;
-      case 'database':
-        return <DatabaseOutlined />;
-      case 'microservice':
-        return <CodeOutlined />;
-      default:
-        return <CodeOutlined />;
-    }
-  };
-
-  // Function to display color tag based on app type
-  const getAppTypeTag = (type: string) => {
-    const colorMap: Record<string, string> = {
-      frontend: 'blue',
-      backend: 'green',
-      api: 'purple',
-      database: 'orange',
-      microservice: 'cyan',
-    };
-    
-    return (
-      <Tag color={colorMap[type] || 'default'}>
-        {type?.toUpperCase()}
-      </Tag>
-    );
-  };
-  
-  return (
-    <Show isLoading={isLoading} 
-      headerButtons={
-        <>
-          <Button type="primary">Deploy App</Button>
-          <Button>Edit App</Button>
-        </>
-      }
-    >
-      <Title level={5}>App Information</Title>
-      
-      <Card>
-        <Descriptions bordered column={2}>
-          <Descriptions.Item label="App Name">{record?.name}</Descriptions.Item>
-          <Descriptions.Item label="Type">
-            {record?.type && (
-              <div>
-                {getAppTypeIcon(record.type)}{' '}
-                {getAppTypeTag(record.type)}
-              </div>
-            )}
-          </Descriptions.Item>
-          <Descriptions.Item label="Language/Framework">
-            <TagField value={record?.language} />
-          </Descriptions.Item>
-          <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
-          <Descriptions.Item label="Description" span={2}>
-            {record?.description}
-          </Descriptions.Item>
-        </Descriptions>
-      </Card>
-
-      <Divider />
-      
-      <Tabs defaultActiveKey="config">
-        <TabPane 
-          tab={<span><CodeOutlined /> Configuration</span>} 
-          key="config"
-        >
-          <Card title="App Configuration">
-            <pre style={{ backgroundColor: '#f5f5f5', padding: 16, borderRadius: 4 }}>
-              {record?.configFile || '{\n  // No configuration available\n}'}
-            </pre>
-          </Card>
-        </TabPane>
-        
-        <TabPane 
-          tab={<span><BranchesOutlined /> Environment</span>} 
-          key="env"
-        >
-          <Card title="Environment Variables">
-            {record?.envVariables ? (
-              <pre style={{ backgroundColor: '#f5f5f5', padding: 16, borderRadius: 4 }}>
-                {record.envVariables}
-              </pre>
-            ) : (
-              <Alert message="No environment variables have been defined" type="info" />
-            )}
-          </Card>
-        </TabPane>
-        
-        <TabPane 
-          tab={<span><CodeOutlined /> Source Code</span>} 
-          key="source"
-        >
-          <Card>
-            <Row gutter={16}>
-              <Col span={12}>
-                <Descriptions title="Repository Information" bordered column={1}>
-                  <Descriptions.Item label="Repository URL">
-                    {record?.repository || 'Not specified'}
-                  </Descriptions.Item>
-                  <Descriptions.Item label="Branch">
-                    {record?.branch || 'main'}
-                  </Descriptions.Item>
-                  <Descriptions.Item label="Build Command">
-                    {record?.buildCommand || 'Not specified'}
-                  </Descriptions.Item>
-                </Descriptions>
-              </Col>
-              <Col span={12}>
-                <Card title="Last Commit" bordered={false}>
-                  <Alert 
-                    message="Source code tracking not enabled" 
-                    description="Connect your repository to track commits and build status"
-                    type="info"
-                  />
-                </Card>
-              </Col>
-            </Row>
-          </Card>
-        </TabPane>
-        
-        <TabPane 
-          tab={<span>⚙️ Resources</span>} 
-          key="resources"
-        >
-          <Card title="Resource Allocation">
-            {record?.resources ? (
-              <pre style={{ backgroundColor: '#f5f5f5', padding: 16, borderRadius: 4 }}>
-                {record.resources}
-              </pre>
-            ) : (
-              <Alert message="No resource requirements specified" type="info" />
-            )}
-          </Card>
-          
-          {record?.type !== 'database' && (
-            <Card title="Scaling Configuration" style={{ marginTop: 16 }}>
-              {record?.scaleSettings ? (
-                <pre style={{ backgroundColor: '#f5f5f5', padding: 16, borderRadius: 4 }}>
-                  {record.scaleSettings}
-                </pre>
-              ) : (
-                <Alert message="No scaling settings defined" type="info" />
-              )}
-            </Card>
-          )}
-        </TabPane>
-      </Tabs>
-    </Show>
-  );
-};

+ 748 - 0
src/pages/blueprints/create.tsx

@@ -0,0 +1,748 @@
+import React, { useState } from 'react';
+import { Create, useForm, useSelect } from "@refinedev/antd";
+import { Form, Input, Button, Card, Table, Modal, Select, InputNumber, Switch, Tabs } from 'antd';
+import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
+import { BlueprintFormData, ComponentConfig, NetworkPolicy, SecretConfig } from '../../types/blueprint';
+
+const { TabPane } = Tabs;
+
+export const BlueprintCreate = () => {
+  const { formProps, saveButtonProps, formLoading } = useForm<BlueprintFormData>({});
+  
+  // Add the component selection hook from the API
+  const { selectProps: componentSelectProps, queryResult: componentsQueryResult } = useSelect({
+    resource: "components",
+    optionLabel: "name",
+    optionValue: "id",
+  });
+  
+  // Helper function to get component name from ID
+  const getComponentNameById = (id: string) => {
+    const component = componentsQueryResult.data?.data.find((item: any) => item.id === id);
+    return component ? component.name : id;
+  };
+
+
+  
+  // Initialize with default values
+  const initialValues: Partial<BlueprintFormData> = {
+    version: "1.0.0",
+    config: {
+      components: [],
+      networkPolicies: [],
+      envVariables: {},
+      secrets: []
+    }
+  };
+
+  const [componentModalVisible, setComponentModalVisible] = useState(false);
+  const [policyModalVisible, setPolicyModalVisible] = useState(false);
+  const [secretModalVisible, setSecretModalVisible] = useState(false);
+  
+  // Current form values through formProps.getFieldsValue()
+  const getFormValues = (): Partial<BlueprintFormData> => {
+    if (!formProps.form) {
+      return {};
+    }
+    return formProps.form.getFieldsValue() || {};
+  };
+  
+  // Components management
+  const [currentComponent, setCurrentComponent] = useState<Partial<ComponentConfig>>({
+    resources: { cpu: '0.5', memory: '512Mi', storage: '1Gi' },
+  });
+  
+  const openComponentModal = () => {
+    setCurrentComponent({
+      resources: { cpu: '0.5', memory: '512Mi', storage: '1Gi' },
+      publicAccess: false,
+      serviceMesh: true,
+    });
+    setComponentModalVisible(true);
+  };
+  
+  const addComponent = () => {
+    if (!currentComponent.id) {
+      // Require a component to be selected
+      Modal.error({
+        title: 'Component Required',
+        content: 'Please select a component from the dropdown'
+      });
+      return;
+    }
+    
+    const values = getFormValues();
+    const components = [...(values?.config?.components || [])];
+    
+    // Check if component already exists in the blueprint
+    const exists = components.some(c => c.id === currentComponent.id);
+    if (exists) {
+      // Show a message that the component already exists
+      Modal.error({
+        title: 'Component Already Exists',
+        content: `The component "${currentComponent.name || currentComponent.id}" has already been added to this blueprint.`
+      });
+      return;
+    }
+    
+    // Find complete component data from query result
+    const componentData = componentsQueryResult.data?.data?.find(
+      (item: any) => item.id === currentComponent.id
+    );
+    
+    components.push({
+      id: currentComponent.id,
+      name: componentData?.name || currentComponent.name || "",
+      resources: currentComponent.resources || { cpu: '0.5', memory: '512Mi', storage: '1Gi' },
+      publicAccess: currentComponent.publicAccess === undefined ? false : currentComponent.publicAccess,
+      serviceMesh: currentComponent.serviceMesh === undefined ? true : currentComponent.serviceMesh,
+    } as ComponentConfig);
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        components
+      }
+    });
+    
+    setComponentModalVisible(false);
+  };
+  
+  const removeComponent = (id: string) => {
+    const values = getFormValues();
+    const components = values.config?.components?.filter(c => c.id !== id) || [];
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        components
+      }
+    });
+  };
+  
+  // Network Policy management
+  const [currentPolicy, setCurrentPolicy] = useState<Partial<NetworkPolicy>>({});
+  
+  // TypeScript helper for policy action
+  const setPolicyAction = (action: 'allow' | 'deny') => {
+    setCurrentPolicy({...currentPolicy, action});
+  };
+  
+  const openPolicyModal = () => {
+    setCurrentPolicy({});
+    setPolicyModalVisible(true);
+  };
+  
+  const addPolicy = () => {
+    // Validate required fields
+    if (!currentPolicy.name) {
+      Modal.error({
+        title: 'Policy Name Required',
+        content: 'Please enter a name for this network policy'
+      });
+      return;
+    }
+    
+    if (!currentPolicy.sourceComponents || currentPolicy.sourceComponents.length === 0) {
+      Modal.error({
+        title: 'Source Components Required',
+        content: 'Please select at least one source component'
+      });
+      return;
+    }
+    
+    if (!currentPolicy.targetComponents || currentPolicy.targetComponents.length === 0) {
+      Modal.error({
+        title: 'Target Components Required',
+        content: 'Please select at least one target component'
+      });
+      return;
+    }
+    
+    const values = getFormValues();
+    const policies = [...(values.config?.networkPolicies || [])];
+    
+    // Check if policy with same name already exists
+    const exists = policies.some(p => p.name === currentPolicy.name);
+    if (exists) {
+      Modal.error({
+        title: 'Policy Already Exists',
+        content: `A network policy with the name "${currentPolicy.name}" already exists.`
+      });
+      return;
+    }
+    
+    policies.push({
+      ...currentPolicy,
+      name: currentPolicy.name,
+      sourceComponents: currentPolicy.sourceComponents || [],
+      targetComponents: currentPolicy.targetComponents || [],
+      ports: currentPolicy.ports || [],
+      protocols: currentPolicy.protocols || ['TCP'],
+      priority: currentPolicy.priority || 100,
+      action: currentPolicy.action || 'allow',
+    } as NetworkPolicy);
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        networkPolicies: policies
+      }
+    });
+    
+    setPolicyModalVisible(false);
+  };
+  
+  const removePolicy = (name: string) => {
+    const values = getFormValues();
+    const policies = values.config?.networkPolicies?.filter(p => p.name !== name) || [];
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        networkPolicies: policies
+      }
+    });
+  };
+  
+  // Secret management
+  const [currentSecret, setCurrentSecret] = useState<Partial<SecretConfig>>({});
+  
+  // TypeScript helper for secret type
+  const setSecretType = (type: 'env' | 'file' | 'key') => {
+    setCurrentSecret({...currentSecret, type});
+  };
+  
+  const openSecretModal = () => {
+    setCurrentSecret({});
+    setSecretModalVisible(true);
+  };
+  
+  const addSecret = () => {
+    const values = getFormValues();
+    const secrets = [...(values.config?.secrets || [])];
+    
+    secrets.push({
+      ...currentSecret,
+      name: currentSecret.name || `secret-${Date.now()}`,
+      type: currentSecret.type || 'env',
+    } as SecretConfig);
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        secrets
+      }
+    });
+    
+    setSecretModalVisible(false);
+  };
+  
+  const removeSecret = (name: string) => {
+    const values = getFormValues();
+    const secrets = values.config?.secrets?.filter(s => s.name !== name) || [];
+    
+    formProps.form?.setFieldsValue({
+      ...values,
+      config: {
+        ...values.config,
+        secrets
+      }
+    });
+  };
+  
+  return (
+    <Create saveButtonProps={saveButtonProps}>
+      <Form {...formProps} layout="vertical" initialValues={initialValues}>
+        <Card title="Blueprint Information" style={{ marginBottom: 20 }}>
+          <Form.Item
+            label="Name"
+            name="name"
+            rules={[{ required: true, message: 'Please enter a blueprint name' }]}
+          >
+            <Input placeholder="Enter blueprint name" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Description"
+            name="description"
+            rules={[{ required: true, message: 'Please enter a description' }]}
+          >
+            <Input.TextArea 
+              rows={4} 
+              placeholder="Describe the purpose of this blueprint" 
+            />
+          </Form.Item>
+          
+          <Form.Item
+            label="Version"
+            name="version"
+            rules={[{ required: true, message: 'Please enter the version' }]}
+          >
+            <Input placeholder="E.g., 1.0.0" />
+          </Form.Item>
+        </Card>
+        
+        <Tabs defaultActiveKey="components">
+          <TabPane tab="Components" key="components">
+            <Card 
+              title="Components" 
+              style={{ marginBottom: 20 }}
+              extra={
+                <Button 
+                  type="primary" 
+                  icon={<PlusOutlined />}
+                  onClick={openComponentModal}
+                >
+                  Add Component
+                </Button>
+              }
+            >
+              <Form.Item name={["config", "components"]} noStyle>
+                <Table 
+                  dataSource={getFormValues()?.config?.components || []}
+                  rowKey="id"
+                  pagination={false}
+                >
+                  <Table.Column title="ID" dataIndex="id" />
+                  <Table.Column title="Name" dataIndex="name" />
+                  <Table.Column 
+                    title="Public" 
+                    dataIndex="publicAccess" 
+                    render={(value) => value ? "Yes" : "No"}
+                  />
+                  <Table.Column 
+                    title="Resources" 
+                    render={(_, record: ComponentConfig) => (
+                      <>
+                        <div>CPU: {record.resources.cpu}</div>
+                        <div>Memory: {record.resources.memory}</div>
+                      </>
+                    )}
+                  />
+                  <Table.Column 
+                    title="Actions"
+                    render={(_, record: ComponentConfig) => (
+                      <Button 
+                        danger 
+                        icon={<DeleteOutlined />}
+                        onClick={() => removeComponent(record.id)}
+                      />
+                    )}
+                  />
+                </Table>
+              </Form.Item>
+            </Card>
+          </TabPane>
+          
+          <TabPane tab="Network Policies" key="policies">
+            <Card 
+              title="Network Policies" 
+              style={{ marginBottom: 20 }}
+              extra={
+                <Button 
+                  type="primary" 
+                  icon={<PlusOutlined />}
+                  onClick={openPolicyModal}
+                >
+                  Add Policy
+                </Button>
+              }
+            >
+              <Form.Item name={["config", "networkPolicies"]} noStyle>
+                <Table 
+                  dataSource={getFormValues()?.config?.networkPolicies || []}
+                  rowKey="name"
+                  pagination={false}
+                >
+                  <Table.Column title="Name" dataIndex="name" />
+                  <Table.Column 
+                    title="Action" 
+                    dataIndex="action"
+                    render={(value) => value === 'allow' ? "Allow" : "Deny"}
+                  />
+                  <Table.Column 
+                    title="Sources" 
+                    dataIndex="sourceComponents"
+                    render={(componentIds: string[] | undefined) => {
+                      if (!componentIds || componentIds.length === 0) {
+                        return '-';
+                      }
+                      // Look up component names from IDs
+                      const componentOptions = componentSelectProps.options || [];
+                      const componentNames = componentIds.map((id: string) => {
+                        const component = componentOptions.find((option: any) => option.value === id);
+                        return component ? component.label : id;
+                      });
+                      return componentNames.join(', ');
+                    }}
+                  />
+                  <Table.Column 
+                    title="Targets" 
+                    dataIndex="targetComponents"
+                    render={(componentIds: string[] | undefined) => {
+                      if (!componentIds || componentIds.length === 0) {
+                        return '-';
+                      }
+                      // Look up component names from IDs
+                      const componentOptions = componentSelectProps.options || [];
+                      const componentNames = componentIds.map((id: string) => {
+                        const component = componentOptions.find((option: any) => option.value === id);
+                        return component ? component.label : id;
+                      });
+                      return componentNames.join(', ');
+                    }}
+                  />
+                  <Table.Column 
+                    title="Actions"
+                    render={(_, record: NetworkPolicy) => (
+                      <Button 
+                        danger 
+                        icon={<DeleteOutlined />}
+                        onClick={() => removePolicy(record.name)}
+                      />
+                    )}
+                  />
+                </Table>
+              </Form.Item>
+            </Card>
+          </TabPane>
+          
+          <TabPane tab="Environment & Secrets" key="env">
+            <Card 
+              title="Environment Variables" 
+              style={{ marginBottom: 20 }}
+            >
+              <Form.Item
+                label="Environment Variables (KEY=value format, one per line)"
+                name={["config", "envVariables"]}
+              >
+                <Input.TextArea rows={6} />
+              </Form.Item>
+            </Card>
+            
+            <Card 
+              title="Secrets" 
+              extra={
+                <Button 
+                  type="primary" 
+                  icon={<PlusOutlined />}
+                  onClick={openSecretModal}
+                >
+                  Add Secret
+                </Button>
+              }
+            >
+              <Form.Item name={["config", "secrets"]} noStyle>
+                <Table 
+                  dataSource={getFormValues()?.config?.secrets || []}
+                  rowKey="name"
+                  pagination={false}
+                >
+                  <Table.Column title="Name" dataIndex="name" />
+                  <Table.Column title="Type" dataIndex="type" />
+                  <Table.Column title="Mount Path" dataIndex="mountPath" />
+                  <Table.Column 
+                    title="Actions"
+                    render={(_, record: SecretConfig) => (
+                      <Button 
+                        danger 
+                        icon={<DeleteOutlined />}
+                        onClick={() => removeSecret(record.name)}
+                      />
+                    )}
+                  />
+                </Table>
+              </Form.Item>
+            </Card>
+          </TabPane>
+        </Tabs>
+      </Form>
+      
+      {/* Component Modal */}
+      <Modal
+        title="Add Component"
+        open={componentModalVisible}
+        onOk={addComponent}
+        onCancel={() => setComponentModalVisible(false)}
+      >
+        <Form layout="vertical">
+          <Form.Item label="Select Component" required>
+            <Select<string>
+              placeholder="Select a component"
+              showSearch
+              filterOption={(input, option) => {
+                if (!option?.label) return false;
+                return option.label.toString().toLowerCase().indexOf(input.toLowerCase()) >= 0;
+              }}
+              value={currentComponent.id}
+              onChange={(value) => {
+                if (!value) return;
+                
+                // When selecting a component, find the component details
+                const selectedComponent = componentsQueryResult.data?.data?.find(
+                  (item: any) => item.id === value
+                );
+                
+                // Update state with the selected component
+                if (selectedComponent) {
+                  setCurrentComponent({
+                    ...currentComponent,
+                    id: selectedComponent.id,
+                    name: selectedComponent.name,
+                    // You might want to prefill other properties from the component data if needed
+                  });
+                }
+              }}
+              options={componentSelectProps.options || []}
+              style={{ width: '100%' }}
+            />
+            {currentComponent.name && (
+              <div style={{ marginTop: 8 }}>
+                Selected: <strong>{currentComponent.name}</strong>
+              </div>
+            )}
+          </Form.Item>
+          
+          <Form.Item label="Public Access">
+            <Switch 
+              checked={currentComponent.publicAccess}
+              onChange={checked => setCurrentComponent({...currentComponent, publicAccess: checked})}
+            />
+          </Form.Item>
+          
+          <Form.Item label="Service Mesh">
+            <Switch 
+              checked={currentComponent.serviceMesh}
+              onChange={checked => setCurrentComponent({...currentComponent, serviceMesh: checked})}
+            />
+          </Form.Item>
+          
+          <Form.Item label="CPU Resources">
+            <Input 
+              value={currentComponent.resources?.cpu}
+              onChange={e => setCurrentComponent({
+                ...currentComponent, 
+                resources: {
+                  cpu: e.target.value,
+                  memory: currentComponent.resources?.memory || '512Mi',
+                  storage: currentComponent.resources?.storage || '1Gi'
+                }
+              })}
+              placeholder="e.g., 0.5, 1, 2"
+            />
+          </Form.Item>
+          
+          <Form.Item label="Memory Resources">
+            <Input 
+              value={currentComponent.resources?.memory}
+              onChange={e => setCurrentComponent({
+                ...currentComponent, 
+                resources: {
+                  cpu: currentComponent.resources?.cpu || '0.5',
+                  memory: e.target.value,
+                  storage: currentComponent.resources?.storage || '1Gi'
+                }
+              })}
+              placeholder="e.g., 512Mi, 1Gi"
+            />
+          </Form.Item>
+          
+          <Form.Item label="Storage Resources">
+            <Input 
+              value={currentComponent.resources?.storage}
+              onChange={e => setCurrentComponent({
+                ...currentComponent, 
+                resources: {
+                  cpu: currentComponent.resources?.cpu || '0.5',
+                  memory: currentComponent.resources?.memory || '512Mi',
+                  storage: e.target.value
+                }
+              })}
+              placeholder="e.g., 1Gi, 10Gi"
+            />
+          </Form.Item>
+        </Form>
+      </Modal>
+      
+      {/* Network Policy Modal */}
+      <Modal
+        title="Add Network Policy"
+        open={policyModalVisible}
+        onOk={addPolicy}
+        onCancel={() => setPolicyModalVisible(false)}
+      >
+        <Form layout="vertical">
+          <Form.Item label="Policy Name" required>
+            <Input 
+              value={currentPolicy.name}
+              onChange={e => setCurrentPolicy({...currentPolicy, name: e.target.value})}
+              placeholder="Policy name"
+            />
+          </Form.Item>
+          
+          <Form.Item label="Source Components" required>
+            <Select<string[]>
+              mode="multiple"
+              placeholder="Select source components" 
+              value={currentPolicy.sourceComponents || []}
+              onChange={(values) => {
+                if (!values) return;
+                setCurrentPolicy({
+                  ...currentPolicy,
+                  sourceComponents: values
+                });
+              }}
+              style={{ width: '100%' }}
+              options={componentSelectProps.options || []}
+              optionFilterProp="label"
+              showSearch
+              filterOption={(input, option) => {
+                if (!option?.label) return false;
+                return option.label.toString().toLowerCase().indexOf(input.toLowerCase()) >= 0;
+              }}
+            />
+            {currentPolicy.sourceComponents && currentPolicy.sourceComponents.length > 0 && (
+              <div style={{ marginTop: 8 }}>
+                Selected: <strong>{currentPolicy.sourceComponents.length}</strong> component(s)
+              </div>
+            )}
+          </Form.Item>
+          
+          <Form.Item label="Target Components" required>
+            <Select<string[]>
+              mode="multiple"
+              placeholder="Select target components"
+              value={currentPolicy.targetComponents || []}
+              onChange={(values) => {
+                if (!values) return;
+                setCurrentPolicy({
+                  ...currentPolicy,
+                  targetComponents: values
+                });
+              }}
+              style={{ width: '100%' }}
+              options={componentSelectProps.options || []}
+              optionFilterProp="label"
+              showSearch
+              filterOption={(input, option) => {
+                if (!option?.label) return false;
+                return option.label.toString().toLowerCase().indexOf(input.toLowerCase()) >= 0;
+              }}
+            />
+            {currentPolicy.targetComponents && currentPolicy.targetComponents.length > 0 && (
+              <div style={{ marginTop: 8 }}>
+                Selected: <strong>{currentPolicy.targetComponents.length}</strong> component(s)
+              </div>
+            )}
+          </Form.Item>
+          
+          <Form.Item label="Ports (comma separated)">
+            <Input 
+              value={currentPolicy.ports?.join(',')}
+              onChange={e => {
+                const portInput = e.target.value;
+                // Handle empty input
+                if (!portInput.trim()) {
+                  setCurrentPolicy({
+                    ...currentPolicy,
+                    ports: []
+                  });
+                  return;
+                }
+                
+                // Parse ports, filtering out any non-numeric values
+                const ports = portInput.split(',')
+                  .map(item => item.trim())
+                  .filter(item => /^\d+$/.test(item))  // Only keep numeric values
+                  .map(item => parseInt(item, 10))
+                  .filter(port => port > 0 && port <= 65535); // Only valid port numbers
+                
+                setCurrentPolicy({
+                  ...currentPolicy, 
+                  ports
+                });
+              }}
+              placeholder="e.g., 80,443,8080"
+            />
+            <div style={{ fontSize: '12px', marginTop: '4px', color: '#888' }}>
+              Valid port numbers: 1-65535
+            </div>
+          </Form.Item>
+          
+          <Form.Item label="Action">
+            <Select
+              value={currentPolicy.action}
+              onChange={(value) => setPolicyAction(value as 'allow' | 'deny')}
+              defaultValue="allow"
+            >
+              <Select.Option value="allow">Allow</Select.Option>
+              <Select.Option value="deny">Deny</Select.Option>
+            </Select>
+          </Form.Item>
+          
+          <Form.Item label="Priority">
+            <InputNumber 
+              value={currentPolicy.priority}
+              onChange={value => setCurrentPolicy({...currentPolicy, priority: value as number})}
+              min={1}
+              defaultValue={100}
+            />
+          </Form.Item>
+        </Form>
+      </Modal>
+      
+      {/* Secret Modal */}
+      <Modal
+        title="Add Secret"
+        open={secretModalVisible}
+        onOk={addSecret}
+        onCancel={() => setSecretModalVisible(false)}
+      >
+        <Form layout="vertical">
+          <Form.Item label="Secret Name" required>
+            <Input 
+              value={currentSecret.name}
+              onChange={e => setCurrentSecret({...currentSecret, name: e.target.value})}
+              placeholder="Secret name"
+            />
+          </Form.Item>
+          
+          <Form.Item label="Secret Type">
+            <Select
+              value={currentSecret.type}
+              onChange={(value) => setSecretType(value as 'env' | 'file' | 'key')}
+              defaultValue="env"
+            >
+              <Select.Option value="env">Environment Variable</Select.Option>
+              <Select.Option value="file">File</Select.Option>
+              <Select.Option value="key">Key</Select.Option>
+            </Select>
+          </Form.Item>
+          
+          <Form.Item label="Mount Path (for file type)">
+            <Input 
+              value={currentSecret.mountPath}
+              onChange={e => setCurrentSecret({...currentSecret, mountPath: e.target.value})}
+              placeholder="/path/to/mount"
+              disabled={currentSecret.type !== 'file'}
+            />
+          </Form.Item>
+          
+          <Form.Item label="Source">
+            <Input 
+              value={currentSecret.source}
+              onChange={e => setCurrentSecret({...currentSecret, source: e.target.value})}
+              placeholder="e.g., env, vault://path"
+            />
+          </Form.Item>
+        </Form>
+      </Modal>
+    </Create>
+  );
+};

+ 0 - 1
src/pages/apps/index.ts → src/pages/blueprints/index.ts

@@ -1,4 +1,3 @@
 export * from "./list";
 export * from "./create";
 export * from "./show";
-

+ 89 - 0
src/pages/blueprints/list.tsx

@@ -0,0 +1,89 @@
+import {
+  DeleteButton,
+  EditButton,
+  List,
+  ShowButton,
+  useTable,
+} from "@refinedev/antd";
+import type { BaseRecord } from "@refinedev/core";
+import { Space, Table, Tag, Button, Tooltip, Typography } from "antd";
+import { PlusOutlined, ApiOutlined, ForkOutlined, LockOutlined } from '@ant-design/icons';
+
+const { Title } = Typography;
+
+export const BlueprintList = () => {
+  const { tableProps } = useTable({
+    syncWithLocation: true,
+  });
+
+  return (
+    <>
+      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
+        <Title level={4}>Deployment Blueprints</Title>
+        <Button type="primary" icon={<PlusOutlined />} href="/blueprints/create">
+          Create New Blueprint
+        </Button>
+      </div>
+
+      <List>
+        <Table {...tableProps} rowKey="id">
+          <Table.Column dataIndex="id" title="ID" />
+          <Table.Column
+            dataIndex="name"
+            title="Name"
+          />
+          <Table.Column
+            dataIndex="description"
+            title="Description"
+          />
+          <Table.Column
+            dataIndex="version"
+            title="Version"
+          />
+          <Table.Column
+            title="Components"
+            render={(_, record: BaseRecord) => (
+              <Tag color="blue">
+                {record?.config?.components?.length || 0} Components
+              </Tag>
+            )}
+          />
+          <Table.Column
+            title="Network Policies"
+            render={(_, record: BaseRecord) => (
+              <Tag color="purple">
+                {record?.config?.networkPolicies?.length || 0} Policies
+              </Tag>
+            )}
+          />
+          <Table.Column
+            title="Actions"
+            dataIndex="actions"
+            render={(_, record: BaseRecord) => (
+              <Space>
+                <Tooltip title="Deploy">
+                  <Button 
+                    type="primary" 
+                    size="small" 
+                    icon={<ApiOutlined />}
+                    href={`/deployments/create?blueprintId=${record.id}`} 
+                  />
+                </Tooltip>
+                <Tooltip title="Clone">
+                  <Button 
+                    size="small" 
+                    icon={<ForkOutlined />}
+                    href={`/blueprints/create?clone=${record.id}`} 
+                  />
+                </Tooltip>
+                <ShowButton hideText size="small" recordItemId={record.id} />
+                <EditButton hideText size="small" recordItemId={record.id} />
+                <DeleteButton hideText size="small" recordItemId={record.id} />
+              </Space>
+            )}
+          />
+        </Table>
+      </List>
+    </>
+  );
+};

+ 227 - 0
src/pages/blueprints/show.tsx

@@ -0,0 +1,227 @@
+import React, { useEffect, useState } from 'react';
+import { Show } from "@refinedev/antd";
+import { useShow, useMany } from "@refinedev/core";
+import { Card, Descriptions, Space, Tag, Typography, Table, Button, Tooltip } from 'antd';
+import { RefreshButton, DateField } from '@refinedev/antd';
+import { ApiOutlined, LinkOutlined, InfoCircleOutlined } from '@ant-design/icons';
+
+const { Title, Text } = Typography;
+
+export const BlueprintShow = () => {
+  const { queryResult } = useShow();
+  const { data, isLoading } = queryResult;
+  const record = data?.data;
+  
+  // Extract component IDs from the blueprint config
+  const componentIds = record?.config?.components?.map((component: {id: string}) => component.id) || [];
+  
+  // Fetch component details for all component IDs
+  const { data: componentsData, isLoading: componentsLoading } = useMany({
+    resource: "components",
+    ids: componentIds,
+    queryOptions: {
+      enabled: componentIds.length > 0,
+    },
+  });
+
+  return (
+    <Show
+      isLoading={isLoading}
+      headerButtons={[
+        <Button 
+          key="deploy" 
+          type="primary" 
+          icon={<ApiOutlined />} 
+          href={`/deployments/create?blueprintId=${record?.id}`}
+        >
+          Deploy
+        </Button>,
+        <RefreshButton key="refresh" />
+      ]}
+    >
+      <Title level={4}>Blueprint Details</Title>
+      
+      <Card style={{ marginBottom: 20 }}>
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="ID">{record?.id}</Descriptions.Item>
+          <Descriptions.Item label="Name">{record?.name}</Descriptions.Item>
+          <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
+          <Descriptions.Item label="Components">
+            <Tag color="blue">{record?.config?.components?.length || 0} Components</Tag>
+          </Descriptions.Item>
+          <Descriptions.Item label="Network Policies">
+            <Tag color="purple">{record?.config?.networkPolicies?.length || 0} Policies</Tag>
+          </Descriptions.Item>
+          <Descriptions.Item label="Description" span={2}>
+            {record?.description}
+          </Descriptions.Item>
+        </Descriptions>
+      </Card>
+      
+      <Card title="Components" style={{ marginBottom: 20 }}>
+        {record?.config?.components && 
+          <Table 
+            dataSource={record.config.components.map((component: any) => {
+              // Find the full component details from the components data
+              const componentDetails = componentsData?.data?.find((c: any) => c.id === component.id);
+              
+              return {
+                ...component,
+                componentDetails,
+              };
+            })}
+            rowKey="id"
+            loading={componentsLoading}
+            pagination={false}
+            expandable={{
+              expandedRowRender: (record: any) => (
+                <Card type="inner" title="Component Details">
+                  <Descriptions bordered column={2} size="small">
+                    <Descriptions.Item label="Description">{record.componentDetails?.description}</Descriptions.Item>
+                    <Descriptions.Item label="Language">{record.componentDetails?.language}</Descriptions.Item>
+                    <Descriptions.Item label="Type">{record.componentDetails?.type}</Descriptions.Item>
+                    <Descriptions.Item label="Version">{record.componentDetails?.version}</Descriptions.Item>
+                  </Descriptions>
+                </Card>
+              ),
+            }}
+          >
+            <Table.Column title="ID" dataIndex="id" />
+            <Table.Column 
+              title="Name" 
+              dataIndex="name" 
+              render={(value, record: any) => (
+                <Space>
+                  {value}
+                  {record.componentDetails && (
+                    <Tooltip title="View Component Details">
+                      <Button 
+                        type="link" 
+                        icon={<InfoCircleOutlined />} 
+                        href={`/components/show/${record.id}`} 
+                        size="small"
+                      />
+                    </Tooltip>
+                  )}
+                </Space>
+              )}
+            />
+            <Table.Column 
+              title="Component Type" 
+              render={(_, record: any) => (
+                <Tag color="blue">{record.componentDetails?.type || "unknown"}</Tag>
+              )}
+            />
+            <Table.Column 
+              title="Public Access" 
+              dataIndex="publicAccess" 
+              render={(value) => value ? <Tag color="green">Yes</Tag> : <Tag color="red">No</Tag>}
+            />
+            <Table.Column 
+              title="Resources" 
+              dataIndex="resources" 
+              render={(resources) => (
+                <>
+                  <div>CPU: {resources.cpu}</div>
+                  <div>Memory: {resources.memory}</div>
+                  <div>Storage: {resources.storage}</div>
+                </>
+              )} 
+            />
+            <Table.Column 
+              title="Autoscaling" 
+              dataIndex="autoscaling" 
+              render={(autoscaling) => autoscaling?.enabled ? (
+                <>
+                  <div>Min: {autoscaling.minReplicas}</div>
+                  <div>Max: {autoscaling.maxReplicas}</div>
+                  <div>CPU: {autoscaling.cpuThreshold}%</div>
+                </>
+              ) : <Tag color="red">Disabled</Tag>} 
+            />
+          </Table>
+        }
+      </Card>
+      
+      <Card title="Network Policies" style={{ marginBottom: 20 }}>
+        {record?.config?.networkPolicies && 
+          <Table 
+            dataSource={record.config.networkPolicies}
+            rowKey="name"
+            pagination={false}
+          >
+            <Table.Column title="Name" dataIndex="name" />
+            <Table.Column 
+              title="Sources" 
+              dataIndex="sourceComponents" 
+              render={(sources) => sources?.map((src: string) => <Tag key={src}>{src}</Tag>)}
+            />
+            <Table.Column 
+              title="Targets" 
+              dataIndex="targetComponents" 
+              render={(targets) => targets?.map((target: string) => <Tag key={target}>{target}</Tag>)}
+            />
+            <Table.Column 
+              title="Ports" 
+              dataIndex="ports" 
+              render={(ports) => ports?.map((port: number) => <Tag key={port}>{port}</Tag>)}
+            />
+            <Table.Column 
+              title="Action" 
+              dataIndex="action" 
+              render={(action) => action === 'allow' ? 
+                <Tag color="green">Allow</Tag> : 
+                <Tag color="red">Deny</Tag>
+              }
+            />
+          </Table>
+        }
+      </Card>
+      
+      <Card title="Environment Variables" style={{ marginBottom: 20 }}>
+        {record?.config?.envVariables && 
+          <Table 
+            dataSource={Object.entries(record.config.envVariables).map(([key, value]) => ({ key, value }))}
+            rowKey="key"
+            pagination={false}
+          >
+            <Table.Column title="Key" dataIndex="key" />
+            <Table.Column title="Value" dataIndex="value" />
+          </Table>
+        }
+        {(!record?.config?.envVariables || Object.keys(record.config.envVariables).length === 0) && 
+          <Text type="secondary">No environment variables defined</Text>
+        }
+      </Card>
+      
+      <Card title="Secrets" style={{ marginBottom: 20 }}>
+        {record?.config?.secrets && 
+          <Table 
+            dataSource={record.config.secrets}
+            rowKey="name"
+            pagination={false}
+          >
+            <Table.Column title="Name" dataIndex="name" />
+            <Table.Column title="Type" dataIndex="type" />
+            <Table.Column title="Mount Path" dataIndex="mountPath" />
+          </Table>
+        }
+        {(!record?.config?.secrets || record.config.secrets.length === 0) && 
+          <Text type="secondary">No secrets defined</Text>
+        }
+      </Card>
+      
+      <Card title="Metadata">
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="Created At">
+            <DateField value={record?.createdAt} format="LLL" />
+          </Descriptions.Item>
+          <Descriptions.Item label="Updated At">
+            <DateField value={record?.updatedAt} format="LLL" />
+          </Descriptions.Item>
+          <Descriptions.Item label="Created By">{record?.createdBy}</Descriptions.Item>
+        </Descriptions>
+      </Card>
+    </Show>
+  );
+};

+ 130 - 0
src/pages/components/create.tsx

@@ -0,0 +1,130 @@
+import React from 'react';
+import { Create, useForm } from "@refinedev/antd";
+import { Form, Input, Select, Button, Card } from 'antd';
+import { ComponentFormData, ComponentType } from '../../types/component';
+
+export const ComponentCreate = () => {
+  const { formProps, saveButtonProps, formLoading } = useForm<ComponentFormData>();
+  
+  return (
+    <Create saveButtonProps={saveButtonProps}>
+      <Form {...formProps} layout="vertical">
+        <Card title="Component Information" style={{ marginBottom: 20 }}>
+          <Form.Item
+            label="Name"
+            name="name"
+            rules={[{ required: true, message: 'Please enter a component name' }]}
+          >
+            <Input placeholder="Enter component name" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Description"
+            name="description"
+            rules={[{ required: true, message: 'Please enter a description' }]}
+          >
+            <Input.TextArea 
+              rows={4} 
+              placeholder="Describe the purpose of this component" 
+            />
+          </Form.Item>
+          
+          <Form.Item
+            label="Type"
+            name="type"
+            rules={[{ required: true, message: 'Please select a component type' }]}
+          >
+            <Select placeholder="Select component type">
+              <Select.Option value="frontend">Frontend</Select.Option>
+              <Select.Option value="backend">Backend</Select.Option>
+              <Select.Option value="api">API</Select.Option>
+              <Select.Option value="database">Database</Select.Option>
+              <Select.Option value="microservice">Microservice</Select.Option>
+            </Select>
+          </Form.Item>
+          
+          <Form.Item
+            label="Language"
+            name="language"
+            rules={[{ required: true, message: 'Please enter the programming language' }]}
+          >
+            <Input placeholder="E.g., JavaScript, Python, Go" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Version"
+            name="version"
+            rules={[{ required: true, message: 'Please enter the version' }]}
+          >
+            <Input placeholder="E.g., 1.0.0" />
+          </Form.Item>
+        </Card>
+        
+        <Card title="Configuration" style={{ marginBottom: 20 }}>
+          <Form.Item
+            label="Config File Path"
+            name="configFile"
+            rules={[{ required: true, message: 'Please enter a config file path' }]}
+          >
+            <Input placeholder="Path to configuration file" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Environment Variables"
+            name="envVariables"
+          >
+            <Input.TextArea 
+              rows={4} 
+              placeholder="KEY=value (one per line)"
+            />
+          </Form.Item>
+        </Card>
+        
+        <Card title="Source Code" style={{ marginBottom: 20 }}>
+          <Form.Item
+            label="Repository URL"
+            name="repository"
+          >
+            <Input placeholder="Git repository URL" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Branch"
+            name="branch"
+          >
+            <Input placeholder="E.g., main, master" />
+          </Form.Item>
+          
+          <Form.Item
+            label="Build Command"
+            name="buildCommand"
+          >
+            <Input placeholder="E.g., npm run build" />
+          </Form.Item>
+        </Card>
+        
+        <Card title="Resources">
+          <Form.Item
+            label="Resource Requirements"
+            name="resources"
+          >
+            <Input.TextArea 
+              rows={4} 
+              placeholder="Resource requirements in YAML format"
+            />
+          </Form.Item>
+          
+          <Form.Item
+            label="Scale Settings"
+            name="scaleSettings"
+          >
+            <Input.TextArea 
+              rows={4} 
+              placeholder="Scaling configuration in YAML format"
+            />
+          </Form.Item>
+        </Card>
+      </Form>
+    </Create>
+  );
+};

+ 2 - 2
src/pages/templates/index.ts → src/pages/components/index.ts

@@ -1,3 +1,3 @@
-export * from "./create";
 export * from "./list";
-export * from "./show";
+export * from "./create";
+export * from "./show";

+ 13 - 12
src/pages/apps/list.tsx → src/pages/components/list.tsx

@@ -12,7 +12,7 @@ import { PlusOutlined, AppstoreOutlined } from "@ant-design/icons";
 
 const { Title } = Typography;
 
-export const AppList = () => {
+export const ComponentList = () => {
   const { tableProps } = useTable({
     syncWithLocation: true,
   });
@@ -20,20 +20,20 @@ export const AppList = () => {
   return (
     <>
       <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
-        <Title level={4}>Apps</Title>
-        <Button type="primary" icon={<PlusOutlined />} href="/apps/create">
-          Create New App
+        <Title level={4}>Components</Title>
+        <Button type="primary" icon={<PlusOutlined />} href="/components/create">
+          Create New Component
         </Button>
       </div>
 
       <List>
         <Table {...tableProps} rowKey="id">
           <Table.Column dataIndex="id" title="ID" />
-          <Table.Column dataIndex="name" title="App Name" />
+          <Table.Column dataIndex="name" title="Component Name" />
           <Table.Column dataIndex="description" title="Description" />
           <Table.Column 
             dataIndex="type" 
-            title="App Type" 
+            title="Component Type" 
             render={(value) => {
               const colorMap: Record<string, string> = {
                 frontend: 'blue',
@@ -50,12 +50,13 @@ export const AppList = () => {
               );
             }}
           />
-          <Table.Column 
-            dataIndex="language" 
-            title="Language/Framework"
-            render={(value) => <TagField value={value} />}
+          <Table.Column
+            dataIndex="language"
+            title="Language"
+            render={(value) => (
+              <TagField value={value} />
+            )}
           />
-          <Table.Column dataIndex="version" title="Version" />
           <Table.Column
             title="Actions"
             dataIndex="actions"
@@ -71,4 +72,4 @@ export const AppList = () => {
       </List>
     </>
   );
-};
+};

+ 197 - 0
src/pages/components/show.tsx

@@ -0,0 +1,197 @@
+import React, { useEffect, useState } from 'react';
+import { Show } from "@refinedev/antd";
+import { useShow, useList } from "@refinedev/core";
+import { Card, Descriptions, Space, Tag, Typography, Table, Button, Alert } from 'antd';
+import { RefreshButton, DateField } from '@refinedev/antd';
+import { LinkOutlined, RocketOutlined } from '@ant-design/icons';
+
+const { Title, Text } = Typography;
+
+export const ComponentShow = () => {
+  const { queryResult } = useShow();
+  const { data, isLoading } = queryResult;
+  const record = data?.data;
+  
+  // Fetch all blueprints to find which ones are using this component
+  const { data: blueprintsData, isLoading: blueprintsLoading } = useList({
+    resource: "blueprints",
+    queryOptions: {
+      enabled: !!record?.id,
+    }
+  });
+  
+  // Fetch deployments to find which ones use this component
+  const { data: deploymentsData, isLoading: deploymentsLoading } = useList({
+    resource: "deployments",
+    queryOptions: {
+      enabled: !!record?.id,
+    }
+  });
+  
+  // Filter blueprints to find which ones use this component
+  const relevantBlueprints = blueprintsData?.data?.filter((blueprint: any) => {
+    return blueprint.config?.components?.some((component: any) => component.id === record?.id);
+  }) || [];
+  
+  // Filter deployments associated with blueprints using this component
+  const relevantDeployments = deploymentsData?.data?.filter((deployment: any) => {
+    // Find a blueprint that uses this component and matches the deployment's blueprintId
+    return relevantBlueprints.some((blueprint: any) => 
+      blueprint.id === deployment.blueprintId
+    );
+  }) || [];
+
+  return (
+    <Show
+      isLoading={isLoading}
+      headerButtons={[<RefreshButton />]}
+    >
+      <Title level={4}>Component Details</Title>
+      
+      <Card style={{ marginBottom: 20 }}>
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="ID">{record?.id}</Descriptions.Item>
+          <Descriptions.Item label="Name">{record?.name}</Descriptions.Item>
+          <Descriptions.Item label="Type">
+            <Tag color="blue">{record?.type}</Tag>
+          </Descriptions.Item>
+          <Descriptions.Item label="Language">{record?.language}</Descriptions.Item>
+          <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
+          <Descriptions.Item label="Description" span={2}>
+            {record?.description}
+          </Descriptions.Item>
+        </Descriptions>
+      </Card>
+      
+      <Card title="Configuration" style={{ marginBottom: 20 }}>
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="Config File" span={2}>
+            {record?.configFile}
+          </Descriptions.Item>
+          <Descriptions.Item label="Environment Variables" span={2}>
+            <pre>{record?.envVariables}</pre>
+          </Descriptions.Item>
+        </Descriptions>
+      </Card>
+      
+      <Card title="Source Code" style={{ marginBottom: 20 }}>
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="Repository">{record?.repository}</Descriptions.Item>
+          <Descriptions.Item label="Branch">{record?.branch}</Descriptions.Item>
+          <Descriptions.Item label="Build Command" span={2}>{record?.buildCommand}</Descriptions.Item>
+        </Descriptions>
+      </Card>
+      
+      <Card title="Resources" style={{ marginBottom: 20 }}>
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="Resources" span={2}>
+            <pre>{record?.resources}</pre>
+          </Descriptions.Item>
+          <Descriptions.Item label="Scale Settings" span={2}>
+            <pre>{record?.scaleSettings}</pre>
+          </Descriptions.Item>
+        </Descriptions>
+      </Card>
+      
+      <Card title="Used in Blueprints" style={{ marginBottom: 20 }}>
+        {blueprintsLoading ? (
+          <div>Loading blueprints...</div>
+        ) : relevantBlueprints.length > 0 ? (
+          <Table
+            dataSource={relevantBlueprints}
+            rowKey="id"
+            pagination={false}
+          >
+            <Table.Column title="ID" dataIndex="id" />
+            <Table.Column 
+              title="Blueprint Name" 
+              dataIndex="name"
+              render={(value, record: any) => (
+                <Button 
+                  type="link"
+                  href={`/blueprints/show/${record.id}`}
+                >
+                  {value}
+                </Button>
+              )}
+            />
+            <Table.Column 
+              title="Version" 
+              dataIndex="version" 
+            />
+            <Table.Column 
+              title="Components" 
+              render={(_, record: any) => (
+                <Tag color="blue">{record.config?.components?.length || 0} Components</Tag>
+              )}
+            />
+          </Table>
+        ) : (
+          <Alert message="This component is not used in any blueprints yet." type="info" />
+        )}
+      </Card>
+      
+      <Card title="Active Deployments" style={{ marginBottom: 20 }}>
+        {deploymentsLoading ? (
+          <div>Loading deployments...</div>
+        ) : relevantDeployments.length > 0 ? (
+          <Table
+            dataSource={relevantDeployments}
+            rowKey="id"
+            pagination={false}
+          >
+            <Table.Column title="ID" dataIndex="id" />
+            <Table.Column 
+              title="Deployment Name" 
+              dataIndex="name"
+              render={(value, record: any) => (
+                <Button 
+                  type="link"
+                  href={`/deployments/show/${record.id}`}
+                >
+                  {value}
+                </Button>
+              )}
+            />
+            <Table.Column 
+              title="Status" 
+              dataIndex="status"
+              render={(value) => (
+                <Tag color={value === 'active' ? 'green' : 'orange'}>
+                  {value.toUpperCase()}
+                </Tag>
+              )}
+            />
+            <Table.Column 
+              title="Client" 
+              dataIndex="clientName" 
+            />
+            <Table.Column 
+              title="Environment" 
+              dataIndex="environment"
+              render={(value) => (
+                <Tag color={value === 'production' ? 'red' : value === 'staging' ? 'orange' : 'green'}>
+                  {value.toUpperCase()}
+                </Tag>
+              )}
+            />
+          </Table>
+        ) : (
+          <Alert message="This component is not used in any active deployments yet." type="info" />
+        )}
+      </Card>
+      
+      <Card title="Metadata">
+        <Descriptions bordered column={2}>
+          <Descriptions.Item label="Created At">
+            <DateField value={record?.createdAt} format="LLL" />
+          </Descriptions.Item>
+          <Descriptions.Item label="Updated At">
+            <DateField value={record?.updatedAt} format="LLL" />
+          </Descriptions.Item>
+          <Descriptions.Item label="Created By">{record?.createdBy}</Descriptions.Item>
+        </Descriptions>
+      </Card>
+    </Show>
+  );
+};

+ 15 - 7
src/pages/dashboard/overview.tsx

@@ -64,15 +64,20 @@ export const DashboardOverview = () => {
     resource: "clients",
   });
 
-  const { data: templatesData } = useList({
-    resource: "templates",
+  const { data: blueprintsData } = useList({
+    resource: "blueprints",
   });
 
+  // Add loading states for dashboard statistics
+  const deploymentsLoading = deploymentsData?.loading || false;
+  const clientsLoading = clientsData?.loading || false;
+  const blueprintsLoading = blueprintsData?.loading || false;
+  
   // Calculate summary statistics
   const totalDeployments = deploymentsData?.data?.length || 0;
   const activeDeployments = deploymentsData?.data?.filter(item => item.status === "active")?.length || 0;
   const totalClients = clientsData?.data?.length || 0;
-  const totalTemplates = templatesData?.data?.length || 0;
+  const totalBlueprints = blueprintsData?.data?.length || 0;
   
   // Mock recent deployments data
   const recentDeployments = [
@@ -109,6 +114,7 @@ export const DashboardOverview = () => {
               value={totalDeployments}
               prefix={<RocketOutlined />}
               valueStyle={{ color: "#3f8600" }}
+              loading={deploymentsLoading}
             />
             <div style={{ marginTop: 8 }}>
               <Text type="secondary">{activeDeployments} active</Text>
@@ -122,6 +128,7 @@ export const DashboardOverview = () => {
               value={totalClients}
               prefix={<UserOutlined />}
               valueStyle={{ color: "#1890ff" }}
+              loading={clientsLoading}
             />
             <div style={{ marginTop: 8 }}>
               <Text type="secondary">Across multiple industries</Text>
@@ -131,10 +138,11 @@ export const DashboardOverview = () => {
         <Col xs={24} sm={12} md={6}>
           <Card>
             <Statistic
-              title="Deployment Templates"
-              value={totalTemplates}
+              title="Deployment Blueprints"
+              value={totalBlueprints}
               prefix={<FileOutlined />}
               valueStyle={{ color: "#722ed1" }}
+              loading={blueprintsLoading}
             />
             <div style={{ marginTop: 8 }}>
               <Text type="secondary">Ready for deployment</Text>
@@ -175,9 +183,9 @@ export const DashboardOverview = () => {
           </Card>
         </Col>
 
-        {/* Template Usage */}
+        {/* Blueprint Usage */}
         <Col xs={24} lg={12}>
-          <Card title="Template Usage">
+          <Card title="Blueprint Usage">
             <Row>
               <Col span={12}>
                 <ResponsiveContainer width="100%" height={250}>

+ 5 - 5
src/pages/deployments/create.tsx

@@ -9,8 +9,8 @@ export const DeploymentCreate = () => {
     resource: "clients",
   });
 
-  const { selectProps: templateSelectProps } = useSelect({
-    resource: "templates",
+  const { selectProps: blueprintSelectProps } = useSelect({
+    resource: "blueprints",
   });
 
   return (
@@ -33,11 +33,11 @@ export const DeploymentCreate = () => {
         </Form.Item>
 
         <Form.Item
-          label={"Template"}
-          name="templateId"
+          label={"Blueprint"}
+          name="blueprintId"
           rules={[{ required: true }]}
         >
-          <Select {...templateSelectProps} placeholder="Select template" />
+          <Select {...blueprintSelectProps} placeholder="Select blueprint" />
         </Form.Item>
 
         <Divider>Configuration</Divider>

+ 12 - 0
src/pages/deployments/list.tsx

@@ -20,6 +20,18 @@ export const DeploymentList = () => {
         <Table.Column dataIndex="id" title={"ID"} />
         <Table.Column dataIndex="name" title={"Deployment Name"} />
         <Table.Column dataIndex="clientName" title={"Client"} />
+        <Table.Column 
+          title={"Blueprint"}
+          render={(_, record: BaseRecord) => (
+            <Space>
+              <Tag color="blue">
+                <a href={`/blueprints/show/${record.blueprintId}`}>
+                  {record.blueprintId?.substring(0, 8)}...
+                </a>
+              </Tag>
+            </Space>
+          )}
+        />
         <Table.Column 
           dataIndex="status" 
           title={"Status"} 

+ 5 - 5
src/pages/deployments/show.tsx

@@ -18,9 +18,9 @@ export const DeploymentShow = () => {
     },
   });
 
-  const { data: templateData, isLoading: templateIsLoading } = useOne({
-    resource: "templates",
-    id: record?.templateId || "",
+  const { data: blueprintData, isLoading: blueprintIsLoading } = useOne({
+    resource: "blueprints",
+    id: record?.blueprintId || "",
     queryOptions: {
       enabled: !!record,
     },
@@ -42,8 +42,8 @@ export const DeploymentShow = () => {
           <Descriptions.Item label="Client">
             {clientIsLoading ? "Loading..." : clientData?.data?.name}
           </Descriptions.Item>
-          <Descriptions.Item label="Template">
-            {templateIsLoading ? "Loading..." : templateData?.data?.name}
+          <Descriptions.Item label="Blueprint">
+            {blueprintIsLoading ? "Loading..." : blueprintData?.data?.name}
           </Descriptions.Item>
           <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
           <Descriptions.Item label="Environment">{record?.environment}</Descriptions.Item>

+ 0 - 535
src/pages/templates/create.tsx

@@ -1,535 +0,0 @@
-import { Create, useForm, useSelect } from "@refinedev/antd";
-import { Form, Input, Tabs, Card, Button, Select, InputNumber, Switch, Space, Divider } from "antd";
-import { useState } from "react";
-import { TemplateFormData, TemplateConfig, AppConfig, NetworkPolicy, SecretConfig } from "../../types/template";
-import { MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
-import { useApiUrl } from "@refinedev/core";
-
-const { TabPane } = Tabs;
-const { TextArea } = Input;
-
-export const TemplateCreate = () => {
-  const apiUrl = useApiUrl();
-  const { formProps, saveButtonProps } = useForm<TemplateFormData>({
-    redirect: "list",
-    onMutationSuccess: (data) => {
-      console.log("Template created successfully:", data);
-    },
-    meta: {
-      onSubmit: (values: { config: { apps: any[]; networkPolicies: any[]; }; }) => {
-        // Convert exposedPorts to integers for all apps
-        if (values.config.apps) {
-          values.config.apps = values.config.apps.map((app: { exposedPorts: any[]; }) => ({
-            ...app,
-            exposedPorts: app.exposedPorts?.map((port: string) => 
-              typeof port === 'string' ? parseInt(port, 10) : port
-            ),
-          }));
-        }
-
-        // Convert network policy ports to integers as well
-        if (values.config.networkPolicies) {
-          values.config.networkPolicies = values.config.networkPolicies.map((policy: { ports: any[]; }) => ({
-            ...policy,
-            ports: policy.ports?.map((port: string) => 
-              typeof port === 'string' ? parseInt(port, 10) : port
-            ),
-          }));
-        }
-        
-        return values;
-      },
-    },
-  });
-  
-  const [activeTabKey, setActiveTabKey] = useState("basic");
-  
-  // For app selection in the template
-  const { selectProps: appSelectProps } = useSelect({
-    resource: "apps",
-    optionLabel: "name",
-    optionValue: "id",
-  });
-
-  // Initialize with default values
-  const initialValues: Partial<TemplateFormData> = {
-    version: "1.0.0",
-    config: {
-      apps: [],
-      networkPolicies: [],
-      envVariables: {},
-      secrets: []
-    }
-  };
-
-  return (
-    <Create saveButtonProps={saveButtonProps}>
-      <Form 
-        {...formProps} 
-        layout="vertical" 
-        initialValues={initialValues}
-        onValuesChange={(_, allValues) => {
-          console.log("Form values:", allValues);
-        }}
-      >
-        <Tabs 
-          activeKey={activeTabKey} 
-          onChange={setActiveTabKey}
-        >
-          <TabPane tab="Basic Information" key="basic">
-            <Card title="Template Details" bordered={false}>
-              <Form.Item
-                label="Template Name"
-                name="name"
-                rules={[{ required: true, message: 'Please enter a template name' }]}
-              >
-                <Input placeholder="Enter template name" />
-              </Form.Item>
-
-              <Form.Item
-                label="Description"
-                name="description"
-                rules={[{ required: true, message: 'Please enter a description' }]}
-              >
-                <TextArea rows={3} placeholder="Describe this template" />
-              </Form.Item>
-
-              <Form.Item
-                label="Version"
-                name="version"
-                rules={[{ required: true, message: 'Please enter a version' }]}
-              >
-                <Input placeholder="1.0.0" />
-              </Form.Item>
-            </Card>
-          </TabPane>
-
-          <TabPane tab="Apps Configuration" key="apps">
-            <Card title="Configure Apps" bordered={false}>
-              <Form.List name={["config", "apps"]}>
-                {(fields, { add, remove }) => (
-                  <>
-                    {fields.map(({ key, name, ...restField }) => (
-                      <Card 
-                        key={key}
-                        title={`App ${name + 1}`}
-                        style={{ marginBottom: 16 }}
-                        extra={
-                          <Button 
-                            type="text" 
-                            onClick={() => remove(name)} 
-                            danger
-                            icon={<MinusCircleOutlined />}
-                          >
-                            Remove
-                          </Button>
-                        }
-                      >
-                        <Form.Item
-                          {...restField}
-                          name={[name, "id"]}
-                          label="App"
-                          rules={[{ required: true, message: 'Please select an app' }]}
-                        >
-                          <Select
-                            {...appSelectProps}
-                            placeholder="Select an app"
-                          />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "name"]}
-                          label="Display Name"
-                          rules={[{ required: true, message: 'Please enter a name for this app instance' }]}
-                        >
-                          <Input placeholder="Name for this app instance" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Exposed Ports"
-                          name={[name, "exposedPorts"]}
-                          getValueFromEvent={(values) => {
-                            return values.map((value: string | number) => 
-                              typeof value === 'string' ? parseInt(value, 10) : value
-                            );
-                          }}
-                        >
-                          <Select
-                            mode="tags"
-                            tokenSeparators={[',']}
-                            placeholder="Enter ports (e.g., 8080, 443)"
-                          />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Public Access"
-                          name={[name, "publicAccess"]}
-                          valuePropName="checked"
-                          initialValue={false}
-                        >
-                          <Switch />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Service Mesh"
-                          name={[name, "serviceMesh"]}
-                          valuePropName="checked"
-                          initialValue={false}
-                        >
-                          <Switch />
-                        </Form.Item>
-
-                        <Divider orientation="left">Resources</Divider>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "resources", "cpu"]}
-                          label="CPU"
-                          rules={[{ required: true, message: 'Please specify CPU resources' }]}
-                          initialValue="0.5"
-                        >
-                          <Input placeholder="0.5" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "resources", "memory"]}
-                          label="Memory"
-                          rules={[{ required: true, message: 'Please specify memory resources' }]}
-                          initialValue="512Mi"
-                        >
-                          <Input placeholder="512Mi" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "resources", "storage"]}
-                          label="Storage"
-                          rules={[{ required: true, message: 'Please specify storage resources' }]}
-                          initialValue="1Gi"
-                        >
-                          <Input placeholder="1Gi" />
-                        </Form.Item>
-
-                        <Divider orientation="left">Autoscaling</Divider>
-
-                        <Form.Item
-                          {...restField}
-                          label="Enable Autoscaling"
-                          name={[name, "autoscaling", "enabled"]}
-                          valuePropName="checked"
-                          initialValue={false}
-                        >
-                          <Switch />
-                        </Form.Item>
-
-                        <Form.Item noStyle shouldUpdate={(prevValues, currentValues) => {
-                          return prevValues?.config?.apps?.[name]?.autoscaling?.enabled !== 
-                                 currentValues?.config?.apps?.[name]?.autoscaling?.enabled;
-                        }}>
-                          {({ getFieldValue }) => {
-                            const enabled = getFieldValue(['config', 'apps', name, 'autoscaling', 'enabled']);
-                            
-                            if (!enabled) {
-                              return null;
-                            }
-
-                            return (
-                              <>
-                                <Form.Item
-                                  {...restField}
-                                  name={[name, "autoscaling", "minReplicas"]}
-                                  label="Min Replicas"
-                                  initialValue={1}
-                                >
-                                  <InputNumber min={1} max={10} />
-                                </Form.Item>
-
-                                <Form.Item
-                                  {...restField}
-                                  name={[name, "autoscaling", "maxReplicas"]}
-                                  label="Max Replicas"
-                                  initialValue={3}
-                                >
-                                  <InputNumber min={1} max={20} />
-                                </Form.Item>
-
-                                <Form.Item
-                                  {...restField}
-                                  name={[name, "autoscaling", "cpuThreshold"]}
-                                  label="CPU Threshold (%)"
-                                  initialValue={80}
-                                >
-                                  <InputNumber min={1} max={100} />
-                                </Form.Item>
-
-                                <Form.Item
-                                  {...restField}
-                                  name={[name, "autoscaling", "metric"]}
-                                  label="Scaling Metric"
-                                  initialValue="cpu"
-                                >
-                                  <Select
-                                    options={[
-                                      { label: "CPU", value: "cpu" },
-                                      { label: "Memory", value: "memory" },
-                                      { label: "Requests", value: "requests" }
-                                    ]}
-                                  />
-                                </Form.Item>
-                              </>
-                            );
-                          }}
-                        </Form.Item>
-
-                        <Divider orientation="left">Environment Overrides</Divider>
-                        <Form.List name={[name, "envOverrides"]}>
-                          {(envFields, { add: addEnv, remove: removeEnv }) => (
-                            <>
-                              {envFields.map(({ key, name: envName, ...restEnvField }) => (
-                                <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
-                                  <Form.Item
-                                    {...restEnvField}
-                                    name={[envName, "key"]}
-                                    rules={[{ required: true, message: 'Missing key' }]}
-                                  >
-                                    <Input placeholder="ENV_KEY" />
-                                  </Form.Item>
-                                  <Form.Item
-                                    {...restEnvField}
-                                    name={[envName, "value"]}
-                                    rules={[{ required: true, message: 'Missing value' }]}
-                                  >
-                                    <Input placeholder="value" />
-                                  </Form.Item>
-                                  <MinusCircleOutlined onClick={() => removeEnv(envName)} />
-                                </Space>
-                              ))}
-                              <Form.Item>
-                                <Button type="dashed" onClick={() => addEnv()} block icon={<PlusOutlined />}>
-                                  Add Environment Variable
-                                </Button>
-                              </Form.Item>
-                            </>
-                          )}
-                        </Form.List>
-                      </Card>
-                    ))}
-                    <Form.Item>
-                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                        Add App
-                      </Button>
-                    </Form.Item>
-                  </>
-                )}
-              </Form.List>
-            </Card>
-          </TabPane>
-
-          <TabPane tab="Network Policies" key="network">
-            <Card title="Network Policies" bordered={false}>
-              <Form.List name={["config", "networkPolicies"]}>
-                {(fields, { add, remove }) => (
-                  <>
-                    {fields.map(({ key, name, ...restField }) => (
-                      <Card 
-                        key={key}
-                        title={`Policy ${name + 1}`}
-                        style={{ marginBottom: 16 }}
-                        extra={
-                          <Button 
-                            type="text" 
-                            onClick={() => remove(name)} 
-                            danger
-                            icon={<MinusCircleOutlined />}
-                          >
-                            Remove
-                          </Button>
-                        }
-                      >
-                        <Form.Item
-                          {...restField}
-                          name={[name, "name"]}
-                          label="Policy Name"
-                          rules={[{ required: true, message: 'Please enter a policy name' }]}
-                        >
-                          <Input placeholder="Enter policy name" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "fromApps"]}
-                          label="Source Apps"
-                          rules={[{ required: true, message: 'Please select source apps' }]}
-                        >
-                          <Select
-                            mode="multiple"
-                            placeholder="Select source apps"
-                          >
-                            {formProps.form?.getFieldValue(['config', 'apps'])?.map((app: AppConfig) => (
-                              <Select.Option key={app.id} value={app.id}>
-                                {app.name}
-                              </Select.Option>
-                            ))}
-                          </Select>
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "toApps"]}
-                          label="Destination Apps"
-                          rules={[{ required: true, message: 'Please select destination apps' }]}
-                        >
-                          <Select
-                            mode="multiple"
-                            placeholder="Select destination apps"
-                          >
-                            {formProps.form?.getFieldValue(['config', 'apps'])?.map((app: AppConfig) => (
-                              <Select.Option key={app.id} value={app.id}>
-                                {app.name}
-                              </Select.Option>
-                            ))}
-                          </Select>
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Allowed Ports"
-                          name={[name, "ports"]}
-                          getValueFromEvent={(values) => {
-                            return values.map((value: string | number) => 
-                              typeof value === 'string' ? parseInt(value, 10) : value
-                            );
-                          }}
-                        >
-                          <Select
-                            mode="tags"
-                            tokenSeparators={[',']}
-                            placeholder="Enter allowed ports (e.g., 8080, 443)"
-                          />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Allow Egress"
-                          name={[name, "allowEgress"]}
-                          valuePropName="checked"
-                          initialValue={true}
-                        >
-                          <Switch />
-                        </Form.Item>
-                      </Card>
-                    ))}
-                    <Form.Item>
-                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                        Add Network Policy
-                      </Button>
-                    </Form.Item>
-                  </>
-                )}
-              </Form.List>
-            </Card>
-          </TabPane>
-
-          <TabPane tab="Environment & Secrets" key="env-secrets">
-            <Card title="Global Environment Variables" bordered={false}>
-              <Form.List name={["config", "envVariables"]}>
-                {(fields, { add, remove }) => (
-                  <>
-                    {fields.map(({ key, name, ...restField }) => (
-                      <Space key={key} style={{ display: 'flex', marginBottom: 8 }} align="baseline">
-                        <Form.Item
-                          {...restField}
-                          name={[name, "key"]}
-                          rules={[{ required: true, message: 'Missing key' }]}
-                        >
-                          <Input placeholder="ENV_KEY" />
-                        </Form.Item>
-                        <Form.Item
-                          {...restField}
-                          name={[name, "value"]}
-                          rules={[{ required: true, message: 'Missing value' }]}
-                        >
-                          <Input placeholder="value" />
-                        </Form.Item>
-                        <MinusCircleOutlined onClick={() => remove(name)} />
-                      </Space>
-                    ))}
-                    <Form.Item>
-                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                        Add Environment Variable
-                      </Button>
-                    </Form.Item>
-                  </>
-                )}
-              </Form.List>
-            </Card>
-
-            <Card title="Secrets Configuration" bordered={false} style={{ marginTop: 16 }}>
-              <Form.List name={["config", "secrets"]}>
-                {(fields, { add, remove }) => (
-                  <>
-                    {fields.map(({ key, name, ...restField }) => (
-                      <Card 
-                        key={key}
-                        title={`Secret ${name + 1}`}
-                        style={{ marginBottom: 16 }}
-                        size="small"
-                        extra={
-                          <Button 
-                            type="text" 
-                            onClick={() => remove(name)} 
-                            danger
-                            icon={<MinusCircleOutlined />}
-                          >
-                            Remove
-                          </Button>
-                        }
-                      >
-                        <Form.Item
-                          {...restField}
-                          name={[name, "name"]}
-                          label="Secret Name"
-                          rules={[{ required: true, message: 'Please enter a secret name' }]}
-                        >
-                          <Input placeholder="Enter secret name" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          name={[name, "description"]}
-                          label="Description"
-                        >
-                          <Input placeholder="Describe this secret" />
-                        </Form.Item>
-
-                        <Form.Item
-                          {...restField}
-                          label="Required"
-                          name={[name, "required"]}
-                          valuePropName="checked"
-                          initialValue={true}
-                        >
-                          <Switch />
-                        </Form.Item>
-                      </Card>
-                    ))}
-                    <Form.Item>
-                      <Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
-                        Add Secret
-                      </Button>
-                    </Form.Item>
-                  </>
-                )}
-              </Form.List>
-            </Card>
-          </TabPane>
-        </Tabs>
-      </Form>
-    </Create>
-  );
-};

+ 0 - 88
src/pages/templates/list.tsx

@@ -1,88 +0,0 @@
-import {
-  DeleteButton,
-  EditButton,
-  List,
-  ShowButton,
-  useTable,
-} from "@refinedev/antd";
-import type { BaseRecord } from "@refinedev/core";
-import { Space, Table, Tag, Button, Tooltip, Typography } from "antd";
-import { PlusOutlined, ApiOutlined, ForkOutlined, LockOutlined } from '@ant-design/icons';
-
-const { Title } = Typography;
-
-export const TemplateList = () => {
-  const { tableProps } = useTable({
-    syncWithLocation: true,
-  });
-
-  return (
-    <>
-      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
-        <Title level={4}>Deployment Templates</Title>
-        <Button type="primary" icon={<PlusOutlined />} href="/templates/create">
-          Create New Template
-        </Button>
-      </div>
-
-      <List>
-        <Table {...tableProps} rowKey="id">
-          <Table.Column dataIndex="id" title="ID" />
-          <Table.Column 
-            dataIndex="name" 
-            title="Template Name"
-            sorter={(a, b) => a.name.localeCompare(b.name)}
-          />
-          <Table.Column dataIndex="description" title="Description" />
-          <Table.Column 
-            title="App Count" 
-            render={(_, record: any) => (
-              record?.config?.apps?.length || 0
-            )}
-          />
-          <Table.Column 
-            title="Features" 
-            render={(_, record: any) => (
-              <Space>
-                {record?.config?.networkPolicies?.length > 0 && (
-                  <Tooltip title="Network Policies">
-                    <ForkOutlined style={{ color: '#1890ff' }} />
-                  </Tooltip>
-                )}
-                {record?.config?.envVariables && Object.keys(record.config.envVariables).length > 0 && (
-                  <Tooltip title="Environment Variables">
-                    <ApiOutlined style={{ color: '#52c41a' }} />
-                  </Tooltip>
-                )}
-                {record?.config?.secrets?.length > 0 && (
-                  <Tooltip title="Secret Configuration">
-                    <LockOutlined style={{ color: '#fa8c16' }} />
-                  </Tooltip>
-                )}
-              </Space>
-            )}
-          />
-          <Table.Column dataIndex="version" title="Version" />
-          <Table.Column 
-            dataIndex="createdBy" 
-            title="Created By" 
-            render={(value) => (
-              <span>{value || "System"}</span>
-            )}
-          />
-          <Table.Column
-            title="Actions"
-            dataIndex="actions"
-            render={(_, record: BaseRecord) => (
-              <Space>
-                <ShowButton hideText size="small" recordItemId={record.id} />
-                <EditButton hideText size="small" recordItemId={record.id} />
-                <DeleteButton hideText size="small" recordItemId={record.id} />
-              </Space>
-            )}
-          />
-        </Table>
-      </List>
-    </>
-  );
-};

+ 0 - 287
src/pages/templates/show.tsx

@@ -1,287 +0,0 @@
-import { Show } from "@refinedev/antd";
-import { useShow } from "@refinedev/core";
-import { 
-  Typography, Card, Descriptions, Tabs, Table, Tag, Badge, 
-  List, Space, Divider, Button, Row, Col, Tooltip 
-} from "antd";
-import { 
-  ApiOutlined, ForkOutlined, LockOutlined, 
-  GlobalOutlined, RocketOutlined, CheckCircleOutlined 
-} from '@ant-design/icons';
-
-const { Title, Text } = Typography;
-const { TabPane } = Tabs;
-
-export const TemplateShow = () => {
-  const { queryResult } = useShow();
-  const { data, isLoading } = queryResult;
-  const record = data?.data;
-
-  // Format environment variables for display
-  const formatEnvVars = (envVars: Record<string, string> = {}) => {
-    return Object.entries(envVars).map(([key, value]) => ({
-      key,
-      value,
-    }));
-  };
-
-  return (
-    <Show isLoading={isLoading}
-      headerButtons={
-        <>
-          <Button type="primary" icon={<RocketOutlined />}>Deploy with This Template</Button>
-          <Button>Edit Template</Button>
-        </>
-      }
-    >
-      <Title level={4}>{record?.name}</Title>
-      <Descriptions bordered column={2} style={{ marginBottom: 20 }}>
-        <Descriptions.Item label="Template ID">{record?.id}</Descriptions.Item>
-        <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
-        <Descriptions.Item label="Created By">{record?.createdBy || 'System'}</Descriptions.Item>
-        <Descriptions.Item label="Created At">{record?.createdAt}</Descriptions.Item>
-        <Descriptions.Item label="Last Updated">{record?.updatedAt}</Descriptions.Item>
-        <Descriptions.Item label="Active Deployments">{record?.deployments?.length || 0}</Descriptions.Item>
-        <Descriptions.Item label="Description" span={2}>
-          {record?.description}
-        </Descriptions.Item>
-      </Descriptions>
-
-      <Tabs defaultActiveKey="apps">
-        <TabPane tab={<span><ApiOutlined /> Apps Configuration</span>} key="apps">
-          <Table 
-            dataSource={record?.config?.apps || []}
-            rowKey="id"
-            pagination={false}
-            expandable={{
-              expandedRowRender: (app: {
-                  resources: any;
-                  autoscaling: any; envOverrides?: Record<string, string> 
-}) => (
-                <div style={{ padding: 16 }}>
-                  <Title level={5}>Resource Configuration</Title>
-                  <Descriptions bordered column={3} size="small">
-                    <Descriptions.Item label="CPU">{app.resources.cpu}</Descriptions.Item>
-                    <Descriptions.Item label="Memory">{app.resources.memory}</Descriptions.Item>
-                    <Descriptions.Item label="Storage">{app.resources.storage}</Descriptions.Item>
-                  </Descriptions>
-                  
-                  {app.autoscaling && app.autoscaling.enabled && (
-                    <>
-                      <Title level={5} style={{ marginTop: 16 }}>Autoscaling</Title>
-                      <Descriptions bordered column={2} size="small">
-                        <Descriptions.Item label="Min Replicas">{app.autoscaling.minReplicas}</Descriptions.Item>
-                        <Descriptions.Item label="Max Replicas">{app.autoscaling.maxReplicas}</Descriptions.Item>
-                        <Descriptions.Item label="CPU Threshold">{app.autoscaling.cpuThreshold}%</Descriptions.Item>
-                        <Descriptions.Item label="Scaling Metric">{app.autoscaling.metric}</Descriptions.Item>
-                      </Descriptions>
-                    </>
-                  )}
-
-                  {app.envOverrides && Object.keys(app.envOverrides).length > 0 && (
-                    <>
-                      <Title level={5} style={{ marginTop: 16 }}>Environment Overrides</Title>
-                      <Table 
-                        dataSource={Object.entries(app.envOverrides || {}).map(([key, value]) => ({ key, value }))}
-                        columns={[
-                          { title: 'Key', dataIndex: 'key' },
-                          { title: 'Value', dataIndex: 'value' }
-                        ]}
-                        size="small"
-                        pagination={false}
-                      />
-                    </>
-                  )}
-                </div>
-              )
-            }}
-            columns={[
-              { title: 'App ID', dataIndex: 'id' },
-              { title: 'Name', dataIndex: 'name' },
-              { 
-                title: 'Ports', 
-                dataIndex: 'exposedPorts',
-                render: (ports: number[]) => (
-                  <Space>
-                    {ports && ports.length > 0 ? ports.map((port) => (
-                      <Tag key={port} color="blue">{port}</Tag>
-                    )) : 'None'}
-                  </Space>
-                )
-              },
-              {
-                title: 'Public Access',
-                dataIndex: 'publicAccess',
-                render: (value: boolean) => (
-                  value ? <Badge status="success" text="Yes" /> : <Badge status="default" text="No" />
-                )
-              },
-              {
-                title: 'Service Mesh',
-                dataIndex: 'serviceMesh',
-                render: (value: boolean) => (
-                  value ? <Badge status="success" text="Yes" /> : <Badge status="default" text="No" />
-                )
-              },
-              {
-                title: 'Autoscaling',
-                dataIndex: 'autoscaling',
-                render: (autoscaling: any) => (
-                  autoscaling?.enabled ? 
-                    <Tooltip title={`${autoscaling.minReplicas}-${autoscaling.maxReplicas} instances`}>
-                      <Badge status="processing" text="Enabled" />
-                    </Tooltip> : 
-                    <Badge status="default" text="Disabled" />
-                )
-              }
-            ]}
-          />
-        </TabPane>
-
-        <TabPane tab={<span><ForkOutlined /> Network Policies</span>} key="network">
-          {record?.config?.networkPolicies?.length > 0 ? (
-            <List
-              itemLayout="vertical"
-              dataSource={record.config.networkPolicies}
-              renderItem={(policy) => (
-                <Card title={policy.name} style={{ marginBottom: 16 }}>
-                  <Row gutter={16}>
-                    <Col span={8}>
-                      <Title level={5}>Source Apps</Title>
-                      <List
-                        size="small"
-                        dataSource={policy.fromApps}
-                        renderItem={(appId) => {
-                          const app = record.config.apps.find(a => a.id === appId);
-                          return (
-                            <List.Item>
-                              {app ? app.name : appId}
-                            </List.Item>
-                          );
-                        }}
-                      />
-                    </Col>
-                    <Col span={8}>
-                      <Title level={5}>Destination Apps</Title>
-                      <List
-                        size="small"
-                        dataSource={policy.toApps}
-                        renderItem={(appId) => {
-                          const app = record.config.apps.find(a => a.id === appId);
-                          return (
-                            <List.Item>
-                              {app ? app.name : appId}
-                            </List.Item>
-                          );
-                        }}
-                      />
-                    </Col>
-                    <Col span={8}>
-                      <Title level={5}>Configuration</Title>
-                      <Descriptions column={1} size="small">
-                        <Descriptions.Item label="Allowed Ports">
-                          {policy.ports && policy.ports.length > 0 ? 
-                            policy.ports.map(port => <Tag key={port}>{port}</Tag>) : 
-                            'All Ports'}
-                        </Descriptions.Item>
-                        <Descriptions.Item label="Egress Traffic">
-                          {policy.allowEgress ? 
-                            <Badge status="success" text="Allowed" /> : 
-                            <Badge status="error" text="Blocked" />}
-                        </Descriptions.Item>
-                      </Descriptions>
-                    </Col>
-                  </Row>
-                </Card>
-              )}
-            />
-          ) : (
-            <Card>
-              <Text type="secondary">No network policies defined for this template.</Text>
-            </Card>
-          )}
-        </TabPane>
-
-        <TabPane tab={<span><GlobalOutlined /> Environment Variables</span>} key="env">
-          {record?.config?.envVariables && Object.keys(record.config.envVariables).length > 0 ? (
-            <Table 
-              dataSource={formatEnvVars(record.config.envVariables)}
-              columns={[
-                { title: 'Key', dataIndex: 'key' },
-                { title: 'Value', dataIndex: 'value' }
-              ]}
-              pagination={false}
-              rowKey="key"
-            />
-          ) : (
-            <Card>
-              <Text type="secondary">No global environment variables defined for this template.</Text>
-            </Card>
-          )}
-        </TabPane>
-
-        <TabPane tab={<span><LockOutlined /> Secrets</span>} key="secrets">
-          {record?.config?.secrets?.length > 0 ? (
-            <Table 
-              dataSource={record.config.secrets}
-              columns={[
-                { title: 'Name', dataIndex: 'name' },
-                { title: 'Description', dataIndex: 'description' },
-                { 
-                  title: 'Required', 
-                  dataIndex: 'required',
-                  render: (value: boolean) => value ? (
-                    <CheckCircleOutlined style={{ color: '#52c41a' }} />
-                  ) : (
-                    <Text type="secondary">Optional</Text>
-                  )
-                }
-              ]}
-              pagination={false}
-              rowKey="name"
-            />
-          ) : (
-            <Card>
-              <Text type="secondary">No secrets defined for this template.</Text>
-            </Card>
-          )}
-        </TabPane>
-
-        <TabPane tab="Deployments" key="deployments">
-          {record?.deployments?.length > 0 ? (
-            <Table
-              dataSource={record.deployments}
-              columns={[
-                { title: 'ID', dataIndex: 'id' },
-                { title: 'Name', dataIndex: 'name' },
-                { title: 'Client', dataIndex: 'clientName' },
-                { 
-                  title: 'Status', 
-                  dataIndex: 'status',
-                  render: (status) => {
-                    const colorMap: Record<string, string> = {
-                      active: 'green',
-                      pending: 'orange',
-                      failed: 'red',
-                      updating: 'blue',
-                      deleting: 'red'
-                    };
-                    
-                    return <Tag color={colorMap[status] || 'default'}>{status.toUpperCase()}</Tag>;
-                  }
-                },
-                { title: 'Created At', dataIndex: 'createdAt' }
-              ]}
-              pagination={false}
-              rowKey="id"
-            />
-          ) : (
-            <Card>
-              <Text type="secondary">No active deployments using this template.</Text>
-            </Card>
-          )}
-        </TabPane>
-      </Tabs>
-    </Show>
-  );
-};

+ 26 - 25
src/types/template.ts → src/types/blueprint.ts

@@ -1,14 +1,16 @@
 /**
- * Template data model that matches the Golang backend implementation
+ * Blueprint data model that matches the Golang backend implementation
  */
-export interface Template {
+
+import { Deployment } from './deployment';
+export interface Blueprint {
   id: string;
   name: string;
   description: string;
   version: string;
   
   // Configuration
-  config: TemplateConfig;
+  config: BlueprintConfig;
   
   // Metadata
   createdAt: string;
@@ -19,14 +21,14 @@ export interface Template {
   deployments?: Deployment[];
 }
 
-export interface TemplateConfig {
-  apps: AppConfig[];
+export interface BlueprintConfig {
+  components: ComponentConfig[];
   networkPolicies: NetworkPolicy[];
   envVariables?: Record<string, string>;
   secrets?: SecretConfig[];
 }
 
-export interface AppConfig {
+export interface ComponentConfig {
   id: string;
   name: string;
   exposedPorts?: number[];
@@ -53,31 +55,30 @@ export interface AutoscalingConfig {
 
 export interface NetworkPolicy {
   name: string;
-  fromApps: string[];
-  toApps: string[];
-  ports?: number[];
-  allowEgress: boolean;
+  sourceComponents: string[];
+  targetComponents: string[];
+  ports: number[];
+  protocols: string[];
+  priority: number;
+  action: 'allow' | 'deny';
 }
 
 export interface SecretConfig {
   name: string;
-  description: string;
-  required: boolean;
+  type: 'env' | 'file' | 'key';
+  value?: string;
+  source?: string;
+  mountPath?: string;
 }
 
-// For integration with existing components
-export interface Deployment {
-  id: string;
-  name: string;
-  // Additional fields omitted for brevity
-}
-
-/**
- * Template form data for creating/editing templates
- */
-export interface TemplateFormData {
+export interface BlueprintFormData {
   name: string;
   description: string;
   version: string;
-  config: TemplateConfig;
-}
+  config: {
+    components: ComponentConfig[];
+    networkPolicies: NetworkPolicy[];
+    envVariables?: Record<string, string>;
+    secrets?: SecretConfig[];
+  };
+}

+ 7 - 7
src/types/app.ts → src/types/component.ts

@@ -1,11 +1,11 @@
 /**
- * App data model that matches the UI components in the app section
+ * Component data model that matches the UI components in the component section
  */
-export interface App {
+export interface Component {
   id: string;
   name: string;
   description: string;
-  type: AppType;
+  type: ComponentType;
   language: string;
   version: string;
   
@@ -27,17 +27,17 @@ export interface App {
   createdBy: string;
 }
 
-export type AppType = 
+export type ComponentType = 
   | 'frontend'
   | 'backend'
   | 'api'
   | 'database'
   | 'microservice';
 
-export interface AppFormData {
+export interface ComponentFormData {
   name: string;
   description: string;
-  type: AppType;
+  type: ComponentType;
   language: string;
   version: string;
   configFile: string;
@@ -47,4 +47,4 @@ export interface AppFormData {
   buildCommand?: string;
   resources?: string;
   scaleSettings?: string;
-}
+}

+ 7 - 7
src/types/deployment.ts

@@ -6,7 +6,7 @@ export interface Deployment {
   name: string;
   clientId: string;
   clientName?: string;
-  templateId: string;
+  blueprintId: string;
   status: DeploymentStatus;
   version: string;
   environment: Environment;
@@ -24,7 +24,7 @@ export interface Deployment {
   createdAt: string;
   
   // Relationships
-  deployedApps?: DeployedApp[];
+  deployedComponents?: DeployedComponent[];
 }
 
 export type DeploymentStatus = 
@@ -45,9 +45,9 @@ export type HealthStatus =
   | 'degraded'
   | 'unhealthy';
 
-export interface DeployedApp {
-  appId: string;
-  status: AppDeploymentStatus;
+export interface DeployedComponent {
+  componentId: string;
+  status: ComponentDeploymentStatus;
   version: string;
   url?: string;
   podCount?: number;
@@ -55,7 +55,7 @@ export interface DeployedApp {
   configSnapshot: string;
 }
 
-export type AppDeploymentStatus = 
+export type ComponentDeploymentStatus = 
   | 'pending'
   | 'running'
   | 'failed'
@@ -65,7 +65,7 @@ export type AppDeploymentStatus =
 export interface DeploymentFormData {
   name: string;
   clientId: string;
-  templateId: string;
+  blueprintId: string;
   version: string;
   containerCount: number;
   environment: Environment;

+ 9 - 0
src/vite-env.d.ts

@@ -1 +1,10 @@
 /// <reference types="vite/client" />
+
+interface ImportMetaEnv {
+  readonly VITE_API_URL: string;
+  // Add other environment variables as needed
+}
+
+interface ImportMeta {
+  readonly env: ImportMetaEnv;
+}

+ 19 - 3
vite.config.ts

@@ -1,6 +1,22 @@
 import react from "@vitejs/plugin-react";
-import { defineConfig } from "vite";
+import { defineConfig, loadEnv } from "vite";
 
-export default defineConfig({
-  plugins: [react()],
+// https://vitejs.dev/config/
+export default defineConfig(({ mode }) => {
+  // Load env file based on `mode` in the current directory.
+  // Set the third parameter to '' to load all env regardless of the `VITE_` prefix.
+  const env = loadEnv(mode, process.cwd(), '');
+
+  return {
+    plugins: [react()],
+    // Make Vite aware of the env variables
+    define: {
+      'process.env': env
+    },
+    // Enable CORS for development
+    server: {
+      cors: true,
+      port: env.PORT ? Number(env.PORT) : 3000,
+    }
+  }
 });