loic boulet před 1 měsícem
rodič
revize
582e39b0dc

+ 1 - 1
.env.local

@@ -1,2 +1,2 @@
 # Local development environment variables
-VITE_API_URL=http://localhost:8080
+VITE_API_URL=http://lboulet.vm.vdi.s1.p.fti.net:443

+ 13 - 13
src/App.tsx

@@ -31,10 +31,10 @@ import {
   DeploymentShow,
 } from "./pages/deployments";
 import {
-  BlueprintList,
-  BlueprintCreate,
-  BlueprintShow,
-} from "./pages/blueprints";
+  AppList,
+  AppCreate,
+  AppShow,
+} from "./pages/apps";
 import {
   ComponentList,
   ComponentCreate,
@@ -94,14 +94,14 @@ function App() {
                     },
                   },
                   {
-                    name: "blueprints",
-                    list: "/blueprints",
-                    create: "/blueprints/create",
-                    show: "/blueprints/show/:id",
+                    name: "apps",
+                    list: "/apps",
+                    create: "/apps/create",
+                    show: "/apps/show/:id",
                     meta: {
                       canDelete: true,
                       icon: <span className="anticon">📄</span>,
-                      label: "Blueprints",
+                      label: "Apps",
                     },
                   },
                   {
@@ -182,10 +182,10 @@ function App() {
                       <Route path="create" element={<DeploymentCreate />} />
                       <Route path="show/:id" element={<DeploymentShow />} />
                     </Route>
-                    <Route path="/blueprints">
-                      <Route index element={<BlueprintList />} />
-                      <Route path="create" element={<BlueprintCreate />} />
-                      <Route path="show/:id" element={<BlueprintShow />} />
+                    <Route path="/apps">
+                      <Route index element={<AppList />} />
+                      <Route path="create" element={<AppCreate />} />
+                      <Route path="show/:id" element={<AppShow />} />
                     </Route>
                     <Route path="/components">
                       <Route index element={<ComponentList />} />

+ 747 - 0
src/pages/app/create.tsx

@@ -0,0 +1,747 @@
+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 { AppFormData, ComponentConfig, NetworkPolicy, SecretConfig } from '../../types/app';
+
+const { TabPane } = Tabs;
+
+export const AppCreate = () => {
+    const { formProps, saveButtonProps, formLoading } = useForm<AppFormData>({});
+
+    // 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<AppFormData> = {
+        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<AppFormData> => {
+        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: { id: string; }) => 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: { name: string; }) => 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: { name: string; }) => 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 - 0
src/pages/app/index.ts


+ 81 - 0
src/pages/app/list.tsx

@@ -0,0 +1,81 @@
+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 AppList = () => {
+    const { tableProps } = useTable({
+        syncWithLocation: true,
+    });
+
+    // ...existing code...
+    return (
+        <>
+            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
+                <Title level={4}>Deployment Apps</Title>
+                <Button type="primary" icon={<PlusOutlined />} href="/apps/create">
+                    Create New App
+                </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?appId=${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>
+        </>
+    );
+}
+// ...existing code...

+ 225 - 0
src/pages/app/show.tsx

@@ -0,0 +1,225 @@
+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 AppShow = () => {
+    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?appId=${record?.id}`}
+                >
+                    Deploy
+                </Button>,
+                <RefreshButton key="refresh" />
+            ]}
+        >
+            <Title level={4}>App 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 >
+    );
+}
+// ...existing code...

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

@@ -0,0 +1,747 @@
+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 { AppFormData, ComponentConfig, NetworkPolicy, SecretConfig } from '../../types/app';
+
+const { TabPane } = Tabs;
+
+export const AppCreate = () => {
+    const { formProps, saveButtonProps, formLoading } = useForm<AppFormData>({});
+
+    // 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<AppFormData> = {
+        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<AppFormData> => {
+        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: { id: string; }) => 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: { name: string; }) => 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: { name: string; }) => 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>
+    );
+};

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

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

+ 81 - 0
src/pages/apps/list.tsx

@@ -0,0 +1,81 @@
+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 AppList = () => {
+    const { tableProps } = useTable({
+        syncWithLocation: true,
+    });
+
+    // ...existing code...
+    return (
+        <>
+            <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
+                <Title level={4}>Deployment Apps</Title>
+                <Button type="primary" icon={<PlusOutlined />} href="/apps/create">
+                    Create New App
+                </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?appId=${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>
+        </>
+    );
+}
+// ...existing code...

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

@@ -0,0 +1,225 @@
+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 AppShow = () => {
+    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?appId=${record?.id}`}
+                >
+                    Deploy
+                </Button>,
+                <RefreshButton key="refresh" />
+            ]}
+        >
+            <Title level={4}>App 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 >
+    );
+}
+// ...existing code...

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

@@ -1,748 +0,0 @@
-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 - 89
src/pages/blueprints/list.tsx

@@ -1,89 +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 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>
-    </>
-  );
-};

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

@@ -1,227 +0,0 @@
-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>
-  );
-};

+ 32 - 32
src/pages/components/show.tsx

@@ -11,7 +11,7 @@ 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",
@@ -19,7 +19,7 @@ export const ComponentShow = () => {
       enabled: !!record?.id,
     }
   });
-  
+
   // Fetch deployments to find which ones use this component
   const { data: deploymentsData, isLoading: deploymentsLoading } = useList({
     resource: "deployments",
@@ -27,17 +27,17 @@ export const ComponentShow = () => {
       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
+    // Find an app that uses this component and matches the deployment's appId
+    return relevantBlueprints.some((blueprint: any) =>
+      blueprint.id === deployment.appId
     );
   }) || [];
 
@@ -47,7 +47,7 @@ export const ComponentShow = () => {
       headerButtons={[<RefreshButton />]}
     >
       <Title level={4}>Component Details</Title>
-      
+
       <Card style={{ marginBottom: 20 }}>
         <Descriptions bordered column={2}>
           <Descriptions.Item label="ID">{record?.id}</Descriptions.Item>
@@ -62,7 +62,7 @@ export const ComponentShow = () => {
           </Descriptions.Item>
         </Descriptions>
       </Card>
-      
+
       <Card title="Configuration" style={{ marginBottom: 20 }}>
         <Descriptions bordered column={2}>
           <Descriptions.Item label="Config File" span={2}>
@@ -73,7 +73,7 @@ export const ComponentShow = () => {
           </Descriptions.Item>
         </Descriptions>
       </Card>
-      
+
       <Card title="Source Code" style={{ marginBottom: 20 }}>
         <Descriptions bordered column={2}>
           <Descriptions.Item label="Repository">{record?.repository}</Descriptions.Item>
@@ -81,7 +81,7 @@ export const ComponentShow = () => {
           <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}>
@@ -92,7 +92,7 @@ export const ComponentShow = () => {
           </Descriptions.Item>
         </Descriptions>
       </Card>
-      
+
       <Card title="Used in Blueprints" style={{ marginBottom: 20 }}>
         {blueprintsLoading ? (
           <div>Loading blueprints...</div>
@@ -103,11 +103,11 @@ export const ComponentShow = () => {
             pagination={false}
           >
             <Table.Column title="ID" dataIndex="id" />
-            <Table.Column 
-              title="Blueprint Name" 
+            <Table.Column
+              title="Blueprint Name"
               dataIndex="name"
               render={(value, record: any) => (
-                <Button 
+                <Button
                   type="link"
                   href={`/blueprints/show/${record.id}`}
                 >
@@ -115,12 +115,12 @@ export const ComponentShow = () => {
                 </Button>
               )}
             />
-            <Table.Column 
-              title="Version" 
-              dataIndex="version" 
+            <Table.Column
+              title="Version"
+              dataIndex="version"
             />
-            <Table.Column 
-              title="Components" 
+            <Table.Column
+              title="Components"
               render={(_, record: any) => (
                 <Tag color="blue">{record.config?.components?.length || 0} Components</Tag>
               )}
@@ -130,7 +130,7 @@ export const ComponentShow = () => {
           <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>
@@ -141,11 +141,11 @@ export const ComponentShow = () => {
             pagination={false}
           >
             <Table.Column title="ID" dataIndex="id" />
-            <Table.Column 
-              title="Deployment Name" 
+            <Table.Column
+              title="Deployment Name"
               dataIndex="name"
               render={(value, record: any) => (
-                <Button 
+                <Button
                   type="link"
                   href={`/deployments/show/${record.id}`}
                 >
@@ -153,8 +153,8 @@ export const ComponentShow = () => {
                 </Button>
               )}
             />
-            <Table.Column 
-              title="Status" 
+            <Table.Column
+              title="Status"
               dataIndex="status"
               render={(value) => (
                 <Tag color={value === 'active' ? 'green' : 'orange'}>
@@ -162,12 +162,12 @@ export const ComponentShow = () => {
                 </Tag>
               )}
             />
-            <Table.Column 
-              title="Client" 
-              dataIndex="clientName" 
+            <Table.Column
+              title="Client"
+              dataIndex="clientName"
             />
-            <Table.Column 
-              title="Environment" 
+            <Table.Column
+              title="Environment"
               dataIndex="environment"
               render={(value) => (
                 <Tag color={value === 'production' ? 'red' : value === 'staging' ? 'orange' : 'green'}>
@@ -180,7 +180,7 @@ export const ComponentShow = () => {
           <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">

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

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

+ 7 - 7
src/pages/deployments/list.tsx

@@ -20,21 +20,21 @@ 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"}
+        <Table.Column
+          title={"App"}
           render={(_, record: BaseRecord) => (
             <Space>
               <Tag color="blue">
-                <a href={`/blueprints/show/${record.blueprintId}`}>
-                  {record.blueprintId?.substring(0, 8)}...
+                <a href={`/apps/show/${record.appId}`}>
+                  App {record.appId}
                 </a>
               </Tag>
             </Space>
           )}
         />
-        <Table.Column 
-          dataIndex="status" 
-          title={"Status"} 
+        <Table.Column
+          dataIndex="status"
+          title={"Status"}
           render={(value) => {
             let color = 'green';
             if (value === 'pending') {

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

@@ -18,9 +18,9 @@ export const DeploymentShow = () => {
     },
   });
 
-  const { data: blueprintData, isLoading: blueprintIsLoading } = useOne({
-    resource: "blueprints",
-    id: record?.blueprintId || "",
+  const { data: appData, isLoading: appIsLoading } = useOne({
+    resource: "apps",
+    id: record?.appId || "",
     queryOptions: {
       enabled: !!record,
     },
@@ -29,21 +29,21 @@ export const DeploymentShow = () => {
   return (
     <Show isLoading={isLoading}>
       <Title level={5}>Deployment Information</Title>
-      
+
       <Card>
         <Descriptions bordered column={2}>
           <Descriptions.Item label="Deployment Name">{record?.name}</Descriptions.Item>
           <Descriptions.Item label="Status">
-            <Badge 
-              status={record?.status === "active" ? "success" : record?.status === "pending" ? "processing" : "error"} 
-              text={record?.status} 
+            <Badge
+              status={record?.status === "active" ? "success" : record?.status === "pending" ? "processing" : "error"}
+              text={record?.status}
             />
           </Descriptions.Item>
           <Descriptions.Item label="Client">
             {clientIsLoading ? "Loading..." : clientData?.data?.name}
           </Descriptions.Item>
-          <Descriptions.Item label="Blueprint">
-            {blueprintIsLoading ? "Loading..." : blueprintData?.data?.name}
+          <Descriptions.Item label="App">
+            {appIsLoading ? "Loading..." : appData?.data?.name}
           </Descriptions.Item>
           <Descriptions.Item label="Version">{record?.version}</Descriptions.Item>
           <Descriptions.Item label="Environment">{record?.environment}</Descriptions.Item>
@@ -57,7 +57,7 @@ export const DeploymentShow = () => {
       </Card>
 
       <Divider />
-      
+
       <Row gutter={24}>
         <Col span={12}>
           <Card title="Health Status">
@@ -78,10 +78,10 @@ export const DeploymentShow = () => {
             <div>
               <div style={{ marginBottom: '10px' }}>CPU</div>
               <Progress percent={record?.cpuUsage || 0} size="small" />
-              
+
               <div style={{ marginBottom: '10px', marginTop: '15px' }}>Memory</div>
               <Progress percent={record?.memoryUsage || 0} size="small" />
-              
+
               <div style={{ marginBottom: '10px', marginTop: '15px' }}>Storage</div>
               <Progress percent={record?.storageUsage || 0} size="small" />
             </div>

+ 79 - 0
src/types/app.ts

@@ -0,0 +1,79 @@
+/**
+ * App data model that matches the Golang backend implementation
+ * (Apps were previously called Templates)
+ */
+
+import { Deployment } from './deployment';
+
+export interface App {
+    id: number;
+    name: string;
+    description: string;
+    version: string;
+    // Configuration
+    config: AppConfig;
+    // Metadata
+    createdAt: string;
+    updatedAt: string;
+    createdBy: number;
+    // Relationships
+    deployments?: Deployment[];
+}
+
+export interface AppConfig {
+    components: ComponentConfig[];
+    networkPolicies: NetworkPolicy[];
+    envVariables?: Record<string, string>;
+    secrets?: SecretConfig[];
+}
+
+export interface ComponentConfig {
+    id: number;
+    name: string;
+    exposedPorts?: number[];
+    publicAccess: boolean;
+    resources: ResourceConfig;
+    autoscaling?: AutoscalingConfig;
+    envOverrides?: Record<string, string>;
+    serviceMesh: boolean;
+}
+
+export interface ResourceConfig {
+    cpu: string;
+    memory: string;
+    storage: string;
+}
+
+export interface AutoscalingConfig {
+    enabled: boolean;
+    minReplicas: number;
+    maxReplicas: number;
+    cpuThreshold: number;
+    metric: string;
+}
+
+export interface NetworkPolicy {
+    name: string;
+    fromComponents: string[];  // Updated to match backend
+    toComponents: string[];    // Updated to match backend
+    ports?: number[];
+    allowEgress: boolean;
+}
+
+export interface SecretConfig {
+    name: string;
+    description: string;
+    required: boolean;
+}
+
+export interface AppFormData {
+    name: string;
+    description: string;
+    version: string;
+    config: {
+        components: ComponentConfig[];
+        networkPolicies: NetworkPolicy[];
+        envVariables?: Record<string, string>;
+        secrets?: SecretConfig[];
+    };
+}

+ 6 - 6
src/types/blueprint.ts

@@ -4,19 +4,19 @@
 
 import { Deployment } from './deployment';
 export interface Blueprint {
-  id: string;
+  id: number;
   name: string;
   description: string;
   version: string;
-  
+
   // Configuration
   config: BlueprintConfig;
-  
+
   // Metadata
   createdAt: string;
   updatedAt: string;
-  createdBy: string;
-  
+  createdBy: number;
+
   // Relationships
   deployments?: Deployment[];
 }
@@ -29,7 +29,7 @@ export interface BlueprintConfig {
 }
 
 export interface ComponentConfig {
-  id: string;
+  id: number;
   name: string;
   exposedPorts?: number[];
   publicAccess: boolean;

+ 4 - 4
src/types/client.ts

@@ -4,7 +4,7 @@ import { Deployment } from './deployment';
  * Client data model that matches the backend API structure
  */
 export interface Client {
-  id: string;
+  id: number;
   name: string;
   contactEmail: string;
   contactPhone?: string;
@@ -13,7 +13,7 @@ export interface Client {
   createdAt: string;
   updatedAt: string;
   deletedAt?: string;
-  
+
   // Relationships
   deployments?: Deployment[];
 }
@@ -37,7 +37,7 @@ export interface Subscription {
   limits: ResourceLimits;
 }
 
-export type PaymentStatus = 
+export type PaymentStatus =
   | 'active'
   | 'pending'
   | 'overdue'
@@ -60,7 +60,7 @@ export interface ClientFormData {
   plan: PlanType;
 }
 
-export type SubscriptionTier = 
+export type SubscriptionTier =
   | 'basic'
   | 'pro'
   | 'enterprise';

+ 7 - 7
src/types/component.ts

@@ -2,32 +2,32 @@
  * Component data model that matches the UI components in the component section
  */
 export interface Component {
-  id: string;
+  id: number;
   name: string;
   description: string;
   type: ComponentType;
   language: string;
   version: string;
-  
+
   // Configuration
   configFile: string;
   envVariables?: string;
-  
+
   // Source code
   repository?: string;
   branch?: string;
   buildCommand?: string;
-  
+
   // Resources
   resources?: string;
   scaleSettings?: string;
-  
+
   createdAt: string;
   updatedAt: string;
-  createdBy: string;
+  createdBy: number;
 }
 
-export type ComponentType = 
+export type ComponentType =
   | 'frontend'
   | 'backend'
   | 'api'

+ 13 - 13
src/types/deployment.ts

@@ -2,32 +2,32 @@
  * Deployment data model that matches the UI components in the deployments section
  */
 export interface Deployment {
-  id: string;
+  id: number;
   name: string;
-  clientId: string;
+  clientId: number;
   clientName?: string;
-  blueprintId: string;
+  appId: number;  // Updated from blueprintId (templates are now apps)
   status: DeploymentStatus;
   version: string;
   environment: Environment;
   containerCount: number;
-  
+
   // Status & monitoring info
   healthStatus?: HealthStatus;
   cpuUsage?: number;
   memoryUsage?: number;
   storageUsage?: number;
-  
+
   // Dates
   deploymentDate: string;
   updatedAt: string;
   createdAt: string;
-  
+
   // Relationships
   deployedComponents?: DeployedComponent[];
 }
 
-export type DeploymentStatus = 
+export type DeploymentStatus =
   | 'pending'
   | 'deploying'
   | 'active'
@@ -35,18 +35,18 @@ export type DeploymentStatus =
   | 'updating'
   | 'deleting';
 
-export type Environment = 
+export type Environment =
   | 'development'
   | 'staging'
   | 'production';
 
-export type HealthStatus = 
+export type HealthStatus =
   | 'healthy'
   | 'degraded'
   | 'unhealthy';
 
 export interface DeployedComponent {
-  componentId: string;
+  componentId: number;
   status: ComponentDeploymentStatus;
   version: string;
   url?: string;
@@ -55,7 +55,7 @@ export interface DeployedComponent {
   configSnapshot: string;
 }
 
-export type ComponentDeploymentStatus = 
+export type ComponentDeploymentStatus =
   | 'pending'
   | 'running'
   | 'failed'
@@ -64,8 +64,8 @@ export type ComponentDeploymentStatus =
 
 export interface DeploymentFormData {
   name: string;
-  clientId: string;
-  blueprintId: string;
+  clientId: number;
+  appId: number;  // Updated from blueprintId (templates are now apps)
   version: string;
   containerCount: number;
   environment: Environment;

+ 5 - 5
src/types/user.ts

@@ -3,21 +3,21 @@
  * Note: Password is intentionally not included in this interface for security
  */
 export interface User {
-  id: string;
+  id: number;
   username: string;
   email: string;
   role: UserRole;
-  clientId?: string; // Only present for client users
+  clientId?: number; // Only present for client users
   isActive: boolean;
   lastLogin?: string;
   preferences: UserPreferences;
   permissions: Permission[];
-  
+
   createdAt: string;
   updatedAt: string;
 }
 
-export type UserRole = 
+export type UserRole =
   | 'system_admin'      // Full system access
   | 'system_manager'    // Platform management but limited admin access
   | 'support'           // Support staff
@@ -31,7 +31,7 @@ export interface Permission {
   constraints?: Record<string, any>;
 }
 
-export type PermissionAction = 
+export type PermissionAction =
   | 'create'
   | 'read'
   | 'update'

+ 1 - 0
vite.config.ts

@@ -17,6 +17,7 @@ export default defineConfig(({ mode }) => {
     server: {
       cors: true,
       port: env.PORT ? Number(env.PORT) : 3000,
+      allowedHosts: ['.localhost', 'lboulet.vm.vdi.s1.p.fti.net']
     }
   }
 });