You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ya...@apache.org on 2025/11/20 19:56:16 UTC

(airavata-portals) branch resource-mgmt created (now a5690bd0f)

This is an automated email from the ASF dual-hosted git repository.

yasith pushed a change to branch resource-mgmt
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git


      at a5690bd0f initial storage and compute resource management functionality

This branch includes the following new commits:

     new a5690bd0f initial storage and compute resource management functionality

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(airavata-portals) 01/01: initial storage and compute resource management functionality

Posted by ya...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

yasith pushed a commit to branch resource-mgmt
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git

commit a5690bd0ff8d010c854d0620df15b3e6eb57f92e
Author: yasithdev <ya...@icloud.com>
AuthorDate: Thu Nov 20 13:56:06 2025 -0600

    initial storage and compute resource management functionality
---
 airavata-research-portal/src/App.tsx               |   2 +
 .../components/resources/ComputeResourceForm.tsx   | 283 +++++++++++++++
 .../src/components/resources/QueueManagement.tsx   | 381 +++++++++++++++++++++
 .../components/resources/ResourceManagement.tsx    | 151 ++++++++
 .../components/resources/StorageResourceForm.tsx   | 218 ++++++++++++
 .../src/interfaces/ComputeResourceType.ts          |  42 +++
 .../src/interfaces/StorageResourceType.ts          |  11 +
 airavata-research-portal/src/lib/resourceApi.ts    | 109 ++++++
 8 files changed, 1197 insertions(+)

diff --git a/airavata-research-portal/src/App.tsx b/airavata-research-portal/src/App.tsx
index 4df295780..99850962a 100644
--- a/airavata-research-portal/src/App.tsx
+++ b/airavata-research-portal/src/App.tsx
@@ -41,6 +41,7 @@ import {AddRepoMaster} from "./components/add/AddRepoMaster";
 import {Add} from "./components/add";
 import {AddProjectMaster} from "./components/add/AddProjectMaster";
 import {StarredResourcesPage} from "@/components/resources/StarredResourcesPage.tsx";
+import {ResourceManagement} from "@/components/resources/ResourceManagement.tsx";
 
 function App() {
   const colorMode = useColorMode();
@@ -120,6 +121,7 @@ function App() {
                 element={<ProtectedComponent Component={NavBarFooterLayout}/>}
             >
               <Route path="/resources/starred" element={<StarredResourcesPage/>}/>
+              <Route path="/resources/manage" element={<ResourceManagement/>}/>
               <Route path="/sessions" element={<Home/>}/>
               <Route path="/add" element={<Add/>}/>
               <Route path="/add/repo" element={<AddRepoMaster/>}/>
diff --git a/airavata-research-portal/src/components/resources/ComputeResourceForm.tsx b/airavata-research-portal/src/components/resources/ComputeResourceForm.tsx
new file mode 100644
index 000000000..9800aa615
--- /dev/null
+++ b/airavata-research-portal/src/components/resources/ComputeResourceForm.tsx
@@ -0,0 +1,283 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  Box,
+  Button,
+  FormControl,
+  FormLabel,
+  Input,
+  Select,
+  Textarea,
+  VStack,
+  HStack,
+  Switch,
+  NumberInput,
+  NumberInputField,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  useToast,
+} from "@chakra-ui/react";
+import { useState, useEffect } from "react";
+import { ComputeResource, ResourceName } from "@/interfaces/ComputeResourceType";
+import { computeResourceApi } from "@/lib/resourceApi";
+import { QueueManagement } from "./QueueManagement";
+
+interface ComputeResourceFormProps {
+  resources: ResourceName[];
+  selectedResource: ComputeResource | null;
+  onSelect: (id: string) => void;
+  onSave: () => void;
+  loading: boolean;
+}
+
+export const ComputeResourceForm = ({
+  resources,
+  selectedResource,
+  onSelect,
+  onSave,
+  loading,
+}: ComputeResourceFormProps) => {
+  const [formData, setFormData] = useState<ComputeResource>({
+    hostName: "",
+  } as ComputeResource);
+  const [saving, setSaving] = useState(false);
+  const toast = useToast();
+
+  useEffect(() => {
+    if (selectedResource) {
+      setFormData(selectedResource);
+    } else {
+      setFormData({ hostName: "" } as ComputeResource);
+    }
+  }, [selectedResource]);
+
+  const handleChange = (field: keyof ComputeResource, value: any) => {
+    setFormData((prev) => ({ ...prev, [field]: value }));
+  };
+
+  const handleSave = async () => {
+    setSaving(true);
+    try {
+      if (formData.computeResourceId) {
+        await computeResourceApi.update(formData.computeResourceId, formData);
+        toast({
+          title: "Success",
+          description: "Compute resource updated successfully",
+          status: "success",
+          duration: 3000,
+        });
+      } else {
+        await computeResourceApi.create(formData);
+        toast({
+          title: "Success",
+          description: "Compute resource created successfully",
+          status: "success",
+          duration: 3000,
+        });
+      }
+      onSave();
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to save compute resource",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  const handleDelete = async () => {
+    if (!formData.computeResourceId) return;
+    if (!confirm("Are you sure you want to delete this compute resource?")) return;
+
+    setSaving(true);
+    try {
+      await computeResourceApi.delete(formData.computeResourceId);
+      toast({
+        title: "Success",
+        description: "Compute resource deleted successfully",
+        status: "success",
+        duration: 3000,
+      });
+      onSave();
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to delete compute resource",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  if (!selectedResource) {
+    return (
+      <Box>
+        {resources.length > 0 ? (
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>ID</Th>
+                <Th>Name</Th>
+                <Th>Actions</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {resources.map((resource) => (
+                <Tr key={resource.id}>
+                  <Td>{resource.id}</Td>
+                  <Td>{resource.name}</Td>
+                  <Td>
+                    <Button size="sm" onClick={() => onSelect(resource.id)}>
+                      Edit
+                    </Button>
+                  </Td>
+                </Tr>
+              ))}
+            </Tbody>
+          </Table>
+        ) : (
+          <Box p={4} textAlign="center">
+            No compute resources found. Click "Create New Compute Resource" to add one.
+          </Box>
+        )}
+      </Box>
+    );
+  }
+
+  return (
+    <Box>
+      <VStack spacing={4} align="stretch">
+        <FormControl>
+          <FormLabel>Host Name *</FormLabel>
+          <Input
+            value={formData.hostName || ""}
+            onChange={(e) => handleChange("hostName", e.target.value)}
+            placeholder="e.g., login.example.com"
+          />
+        </FormControl>
+
+        <FormControl>
+          <FormLabel>Resource Description</FormLabel>
+          <Textarea
+            value={formData.resourceDescription || ""}
+            onChange={(e) => handleChange("resourceDescription", e.target.value)}
+            placeholder="Description of the compute resource"
+          />
+        </FormControl>
+
+        <FormControl>
+          <FormLabel>Enabled</FormLabel>
+          <Switch
+            isChecked={formData.enabled ?? true}
+            onChange={(e) => handleChange("enabled", e.target.checked)}
+          />
+        </FormControl>
+
+        <HStack>
+          <FormControl>
+            <FormLabel>CPUs Per Node</FormLabel>
+            <NumberInput
+              value={formData.cpusPerNode || ""}
+              onChange={(_, value) => handleChange("cpusPerNode", value)}
+            >
+              <NumberInputField />
+            </NumberInput>
+          </FormControl>
+
+          <FormControl>
+            <FormLabel>Max Memory Per Node (MB)</FormLabel>
+            <NumberInput
+              value={formData.maxMemoryPerNode || ""}
+              onChange={(_, value) => handleChange("maxMemoryPerNode", value)}
+            >
+              <NumberInputField />
+            </NumberInput>
+          </FormControl>
+        </HStack>
+
+        <HStack>
+          <FormControl>
+            <FormLabel>Default Node Count</FormLabel>
+            <NumberInput
+              value={formData.defaultNodeCount || ""}
+              onChange={(_, value) => handleChange("defaultNodeCount", value)}
+            >
+              <NumberInputField />
+            </NumberInput>
+          </FormControl>
+
+          <FormControl>
+            <FormLabel>Default CPU Count</FormLabel>
+            <NumberInput
+              value={formData.defaultCPUCount || ""}
+              onChange={(_, value) => handleChange("defaultCPUCount", value)}
+            >
+              <NumberInputField />
+            </NumberInput>
+          </FormControl>
+
+          <FormControl>
+            <FormLabel>Default Walltime (minutes)</FormLabel>
+            <NumberInput
+              value={formData.defaultWalltime || ""}
+              onChange={(_, value) => handleChange("defaultWalltime", value)}
+            >
+              <NumberInputField />
+            </NumberInput>
+          </FormControl>
+        </HStack>
+
+        {formData.computeResourceId && (
+          <QueueManagement
+            computeResourceId={formData.computeResourceId}
+            queues={formData.batchQueues || []}
+            onQueuesChange={(queues) => handleChange("batchQueues", queues)}
+          />
+        )}
+
+        <HStack>
+          <Button
+            colorScheme="blue"
+            onClick={handleSave}
+            isLoading={saving}
+            loadingText="Saving..."
+          >
+            {formData.computeResourceId ? "Update" : "Create"}
+          </Button>
+          {formData.computeResourceId && (
+            <Button colorScheme="red" onClick={handleDelete} isLoading={saving}>
+              Delete
+            </Button>
+          )}
+          <Button onClick={() => onSelect("")}>Back to List</Button>
+        </HStack>
+      </VStack>
+    </Box>
+  );
+};
+
diff --git a/airavata-research-portal/src/components/resources/QueueManagement.tsx b/airavata-research-portal/src/components/resources/QueueManagement.tsx
new file mode 100644
index 000000000..645e68098
--- /dev/null
+++ b/airavata-research-portal/src/components/resources/QueueManagement.tsx
@@ -0,0 +1,381 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  Box,
+  Button,
+  FormControl,
+  FormLabel,
+  Input,
+  VStack,
+  HStack,
+  Switch,
+  NumberInput,
+  NumberInputField,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  Textarea,
+  Modal,
+  ModalOverlay,
+  ModalContent,
+  ModalHeader,
+  ModalBody,
+  ModalFooter,
+  ModalCloseButton,
+  useDisclosure,
+  useToast,
+} from "@chakra-ui/react";
+import { useState } from "react";
+import { BatchQueue } from "@/interfaces/ComputeResourceType";
+import { computeResourceApi } from "@/lib/resourceApi";
+
+interface QueueManagementProps {
+  computeResourceId: string;
+  queues: BatchQueue[];
+  onQueuesChange: (queues: BatchQueue[]) => void;
+}
+
+export const QueueManagement = ({
+  computeResourceId,
+  queues,
+  onQueuesChange,
+}: QueueManagementProps) => {
+  const { isOpen, onOpen, onClose } = useDisclosure();
+  const [editingQueue, setEditingQueue] = useState<BatchQueue | null>(null);
+  const [queueForm, setQueueForm] = useState<BatchQueue>({
+    queueName: "",
+  } as BatchQueue);
+  const [saving, setSaving] = useState(false);
+  const toast = useToast();
+
+  const handleAddQueue = () => {
+    setEditingQueue(null);
+    setQueueForm({ queueName: "" } as BatchQueue);
+    onOpen();
+  };
+
+  const handleEditQueue = (queue: BatchQueue) => {
+    setEditingQueue(queue);
+    setQueueForm({ ...queue });
+    onOpen();
+  };
+
+  const handleDeleteQueue = async (queueName: string) => {
+    if (!confirm(`Are you sure you want to delete queue "${queueName}"?`)) return;
+
+    setSaving(true);
+    try {
+      await computeResourceApi.deleteQueue(computeResourceId, queueName);
+      const updatedQueues = queues.filter((q) => q.queueName !== queueName);
+      onQueuesChange(updatedQueues);
+      toast({
+        title: "Success",
+        description: "Queue deleted successfully",
+        status: "success",
+        duration: 3000,
+      });
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to delete queue",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  const handleSaveQueue = async () => {
+    if (!queueForm.queueName) {
+      toast({
+        title: "Error",
+        description: "Queue name is required",
+        status: "error",
+        duration: 3000,
+      });
+      return;
+    }
+
+    setSaving(true);
+    try {
+      if (editingQueue) {
+        await computeResourceApi.updateQueue(computeResourceId, editingQueue.queueName, queueForm);
+        const updatedQueues = queues.map((q) =>
+          q.queueName === editingQueue.queueName ? queueForm : q
+        );
+        onQueuesChange(updatedQueues);
+        toast({
+          title: "Success",
+          description: "Queue updated successfully",
+          status: "success",
+          duration: 3000,
+        });
+      } else {
+        await computeResourceApi.addQueue(computeResourceId, queueForm);
+        onQueuesChange([...queues, queueForm]);
+        toast({
+          title: "Success",
+          description: "Queue added successfully",
+          status: "success",
+          duration: 3000,
+        });
+      }
+      onClose();
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to save queue",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  return (
+    <Box>
+      <HStack mb={4}>
+        <Button size="sm" onClick={handleAddQueue}>
+          Add Queue
+        </Button>
+      </HStack>
+
+      <Table variant="simple">
+        <Thead>
+          <Tr>
+            <Th>Queue Name</Th>
+            <Th>Description</Th>
+            <Th>Max Runtime</Th>
+            <Th>Max Nodes</Th>
+            <Th>Default Queue</Th>
+            <Th>Actions</Th>
+          </Tr>
+        </Thead>
+        <Tbody>
+          {queues.map((queue) => (
+            <Tr key={queue.queueName}>
+              <Td>{queue.queueName}</Td>
+              <Td>{queue.queueDescription || "-"}</Td>
+              <Td>{queue.maxRunTime || "-"}</Td>
+              <Td>{queue.maxNodes || "-"}</Td>
+              <Td>{queue.isDefaultQueue ? "Yes" : "No"}</Td>
+              <Td>
+                <HStack>
+                  <Button size="sm" onClick={() => handleEditQueue(queue)}>
+                    Edit
+                  </Button>
+                  <Button
+                    size="sm"
+                    colorScheme="red"
+                    onClick={() => handleDeleteQueue(queue.queueName)}
+                  >
+                    Delete
+                  </Button>
+                </HStack>
+              </Td>
+            </Tr>
+          ))}
+        </Tbody>
+      </Table>
+
+      <Modal isOpen={isOpen} onClose={onClose} size="xl">
+        <ModalOverlay />
+        <ModalContent>
+          <ModalHeader>{editingQueue ? "Edit Queue" : "Add Queue"}</ModalHeader>
+          <ModalCloseButton />
+          <ModalBody>
+            <VStack spacing={4} align="stretch">
+              <FormControl isRequired>
+                <FormLabel>Queue Name</FormLabel>
+                <Input
+                  value={queueForm.queueName || ""}
+                  onChange={(e) => setQueueForm({ ...queueForm, queueName: e.target.value })}
+                  isDisabled={!!editingQueue}
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormLabel>Queue Description</FormLabel>
+                <Textarea
+                  value={queueForm.queueDescription || ""}
+                  onChange={(e) =>
+                    setQueueForm({ ...queueForm, queueDescription: e.target.value })
+                  }
+                />
+              </FormControl>
+
+              <HStack>
+                <FormControl>
+                  <FormLabel>Max Runtime (hours)</FormLabel>
+                  <NumberInput
+                    value={queueForm.maxRunTime || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, maxRunTime: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+
+                <FormControl>
+                  <FormLabel>Max Nodes</FormLabel>
+                  <NumberInput
+                    value={queueForm.maxNodes || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, maxNodes: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+              </HStack>
+
+              <HStack>
+                <FormControl>
+                  <FormLabel>Max Processors</FormLabel>
+                  <NumberInput
+                    value={queueForm.maxProcessors || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, maxProcessors: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+
+                <FormControl>
+                  <FormLabel>Max Jobs in Queue</FormLabel>
+                  <NumberInput
+                    value={queueForm.maxJobsInQueue || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, maxJobsInQueue: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+              </HStack>
+
+              <HStack>
+                <FormControl>
+                  <FormLabel>Max Memory (MB)</FormLabel>
+                  <NumberInput
+                    value={queueForm.maxMemory || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, maxMemory: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+
+                <FormControl>
+                  <FormLabel>CPU Per Node</FormLabel>
+                  <NumberInput
+                    value={queueForm.cpuPerNode || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, cpuPerNode: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+              </HStack>
+
+              <HStack>
+                <FormControl>
+                  <FormLabel>Default Node Count</FormLabel>
+                  <NumberInput
+                    value={queueForm.defaultNodeCount || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, defaultNodeCount: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+
+                <FormControl>
+                  <FormLabel>Default CPU Count</FormLabel>
+                  <NumberInput
+                    value={queueForm.defaultCPUCount || ""}
+                    onChange={(_, value) =>
+                      setQueueForm({ ...queueForm, defaultCPUCount: value })
+                    }
+                  >
+                    <NumberInputField />
+                  </NumberInput>
+                </FormControl>
+              </HStack>
+
+              <FormControl>
+                <FormLabel>Default Walltime (minutes)</FormLabel>
+                <NumberInput
+                  value={queueForm.defaultWalltime || ""}
+                  onChange={(_, value) =>
+                    setQueueForm({ ...queueForm, defaultWalltime: value })
+                  }
+                >
+                  <NumberInputField />
+                </NumberInput>
+              </FormControl>
+
+              <FormControl>
+                <FormLabel>Queue Specific Macros</FormLabel>
+                <Textarea
+                  value={queueForm.queueSpecificMacros || ""}
+                  onChange={(e) =>
+                    setQueueForm({ ...queueForm, queueSpecificMacros: e.target.value })
+                  }
+                  placeholder="Comma-separated macros"
+                />
+              </FormControl>
+
+              <FormControl>
+                <FormLabel>Is Default Queue</FormLabel>
+                <Switch
+                  isChecked={queueForm.isDefaultQueue || false}
+                  onChange={(e) =>
+                    setQueueForm({ ...queueForm, isDefaultQueue: e.target.checked })
+                  }
+                />
+              </FormControl>
+            </VStack>
+          </ModalBody>
+          <ModalFooter>
+            <Button colorScheme="blue" onClick={handleSaveQueue} isLoading={saving}>
+              {editingQueue ? "Update" : "Add"}
+            </Button>
+            <Button onClick={onClose} ml={2}>
+              Cancel
+            </Button>
+          </ModalFooter>
+        </ModalContent>
+      </Modal>
+    </Box>
+  );
+};
+
+
+
diff --git a/airavata-research-portal/src/components/resources/ResourceManagement.tsx b/airavata-research-portal/src/components/resources/ResourceManagement.tsx
new file mode 100644
index 000000000..83388d08c
--- /dev/null
+++ b/airavata-research-portal/src/components/resources/ResourceManagement.tsx
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  Box,
+  Button,
+  Container,
+  Heading,
+  Tab,
+  TabList,
+  TabPanel,
+  TabPanels,
+  Tabs,
+} from "@chakra-ui/react";
+import { useState, useEffect } from "react";
+import { ComputeResourceForm } from "./ComputeResourceForm";
+import { StorageResourceForm } from "./StorageResourceForm";
+import { computeResourceApi, storageResourceApi } from "@/lib/resourceApi";
+import { ComputeResource } from "@/interfaces/ComputeResourceType";
+import { StorageResource } from "@/interfaces/StorageResourceType";
+import { ResourceName } from "@/interfaces/ComputeResourceType";
+
+export const ResourceManagement = () => {
+  const [computeResources, setComputeResources] = useState<ResourceName[]>([]);
+  const [storageResources, setStorageResources] = useState<ResourceName[]>([]);
+  const [selectedComputeResource, setSelectedComputeResource] = useState<ComputeResource | null>(null);
+  const [selectedStorageResource, setSelectedStorageResource] = useState<StorageResource | null>(null);
+  const [loading, setLoading] = useState(false);
+
+  useEffect(() => {
+    loadResources();
+  }, []);
+
+  const loadResources = async () => {
+    setLoading(true);
+    try {
+      const [compute, storage] = await Promise.all([
+        computeResourceApi.getAll(),
+        storageResourceApi.getAll(),
+      ]);
+      setComputeResources(compute);
+      setStorageResources(storage);
+    } catch (error) {
+      console.error("Error loading resources:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const handleComputeResourceSelect = async (id: string) => {
+    if (!id) {
+      setSelectedComputeResource(null);
+      return;
+    }
+    try {
+      const resource = await computeResourceApi.get(id);
+      setSelectedComputeResource(resource);
+    } catch (error) {
+      console.error("Error loading compute resource:", error);
+    }
+  };
+
+  const handleStorageResourceSelect = async (id: string) => {
+    if (!id) {
+      setSelectedStorageResource(null);
+      return;
+    }
+    try {
+      const resource = await storageResourceApi.get(id);
+      setSelectedStorageResource(resource);
+    } catch (error) {
+      console.error("Error loading storage resource:", error);
+    }
+  };
+
+  const handleComputeResourceSave = async () => {
+    await loadResources();
+    setSelectedComputeResource(null);
+  };
+
+  const handleStorageResourceSave = async () => {
+    await loadResources();
+    setSelectedStorageResource(null);
+  };
+
+  return (
+    <Container maxW="container.xl" mt={8}>
+      <Heading mb={6}>Resource Management</Heading>
+      <Tabs>
+        <TabList>
+          <Tab>Compute Resources</Tab>
+          <Tab>Storage Resources</Tab>
+        </TabList>
+
+        <TabPanels>
+          <TabPanel>
+            <Box>
+              <Button
+                mb={4}
+                onClick={() => setSelectedComputeResource({ hostName: "" } as ComputeResource)}
+              >
+                Create New Compute Resource
+              </Button>
+              <ComputeResourceForm
+                resources={computeResources}
+                selectedResource={selectedComputeResource}
+                onSelect={handleComputeResourceSelect}
+                onSave={handleComputeResourceSave}
+                loading={loading}
+              />
+            </Box>
+          </TabPanel>
+
+          <TabPanel>
+            <Box>
+              <Button
+                mb={4}
+                onClick={() => setSelectedStorageResource({ hostName: "" } as StorageResource)}
+              >
+                Create New Storage Resource
+              </Button>
+              <StorageResourceForm
+                resources={storageResources}
+                selectedResource={selectedStorageResource}
+                onSelect={handleStorageResourceSelect}
+                onSave={handleStorageResourceSave}
+                loading={loading}
+              />
+            </Box>
+          </TabPanel>
+        </TabPanels>
+      </Tabs>
+    </Container>
+  );
+};
+
diff --git a/airavata-research-portal/src/components/resources/StorageResourceForm.tsx b/airavata-research-portal/src/components/resources/StorageResourceForm.tsx
new file mode 100644
index 000000000..c78c37a9d
--- /dev/null
+++ b/airavata-research-portal/src/components/resources/StorageResourceForm.tsx
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import {
+  Box,
+  Button,
+  FormControl,
+  FormLabel,
+  Input,
+  Textarea,
+  VStack,
+  HStack,
+  Switch,
+  Table,
+  Thead,
+  Tbody,
+  Tr,
+  Th,
+  Td,
+  useToast,
+} from "@chakra-ui/react";
+import { useState, useEffect } from "react";
+import { StorageResource } from "@/interfaces/StorageResourceType";
+import { ResourceName } from "@/interfaces/ComputeResourceType";
+import { storageResourceApi } from "@/lib/resourceApi";
+
+interface StorageResourceFormProps {
+  resources: ResourceName[];
+  selectedResource: StorageResource | null;
+  onSelect: (id: string) => void;
+  onSave: () => void;
+  loading: boolean;
+}
+
+export const StorageResourceForm = ({
+  resources,
+  selectedResource,
+  onSelect,
+  onSave,
+  loading,
+}: StorageResourceFormProps) => {
+  const [formData, setFormData] = useState<StorageResource>({
+    hostName: "",
+  } as StorageResource);
+  const [saving, setSaving] = useState(false);
+  const toast = useToast();
+
+  useEffect(() => {
+    if (selectedResource) {
+      setFormData(selectedResource);
+    } else {
+      setFormData({ hostName: "" } as StorageResource);
+    }
+  }, [selectedResource]);
+
+  const handleChange = (field: keyof StorageResource, value: any) => {
+    setFormData((prev) => ({ ...prev, [field]: value }));
+  };
+
+  const handleSave = async () => {
+    setSaving(true);
+    try {
+      if (formData.storageResourceId) {
+        await storageResourceApi.update(formData.storageResourceId, formData);
+        toast({
+          title: "Success",
+          description: "Storage resource updated successfully",
+          status: "success",
+          duration: 3000,
+        });
+      } else {
+        await storageResourceApi.create(formData);
+        toast({
+          title: "Success",
+          description: "Storage resource created successfully",
+          status: "success",
+          duration: 3000,
+        });
+      }
+      onSave();
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to save storage resource",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  const handleDelete = async () => {
+    if (!formData.storageResourceId) return;
+    if (!confirm("Are you sure you want to delete this storage resource?")) return;
+
+    setSaving(true);
+    try {
+      await storageResourceApi.delete(formData.storageResourceId);
+      toast({
+        title: "Success",
+        description: "Storage resource deleted successfully",
+        status: "success",
+        duration: 3000,
+      });
+      onSave();
+    } catch (error: any) {
+      toast({
+        title: "Error",
+        description: error.response?.data?.error || "Failed to delete storage resource",
+        status: "error",
+        duration: 5000,
+      });
+    } finally {
+      setSaving(false);
+    }
+  };
+
+  if (!selectedResource) {
+    return (
+      <Box>
+        {resources.length > 0 ? (
+          <Table variant="simple">
+            <Thead>
+              <Tr>
+                <Th>ID</Th>
+                <Th>Name</Th>
+                <Th>Actions</Th>
+              </Tr>
+            </Thead>
+            <Tbody>
+              {resources.map((resource) => (
+                <Tr key={resource.id}>
+                  <Td>{resource.id}</Td>
+                  <Td>{resource.name}</Td>
+                  <Td>
+                    <Button size="sm" onClick={() => onSelect(resource.id)}>
+                      Edit
+                    </Button>
+                  </Td>
+                </Tr>
+              ))}
+            </Tbody>
+          </Table>
+        ) : (
+          <Box p={4} textAlign="center">
+            No storage resources found. Click "Create New Storage Resource" to add one.
+          </Box>
+        )}
+      </Box>
+    );
+  }
+
+  return (
+    <Box>
+      <VStack spacing={4} align="stretch">
+        <FormControl>
+          <FormLabel>Host Name *</FormLabel>
+          <Input
+            value={formData.hostName || ""}
+            onChange={(e) => handleChange("hostName", e.target.value)}
+            placeholder="e.g., storage.example.com"
+          />
+        </FormControl>
+
+        <FormControl>
+          <FormLabel>Storage Resource Description</FormLabel>
+          <Textarea
+            value={formData.storageResourceDescription || ""}
+            onChange={(e) => handleChange("storageResourceDescription", e.target.value)}
+            placeholder="Description of the storage resource"
+          />
+        </FormControl>
+
+        <FormControl>
+          <FormLabel>Enabled</FormLabel>
+          <Switch
+            isChecked={formData.enabled ?? true}
+            onChange={(e) => handleChange("enabled", e.target.checked)}
+          />
+        </FormControl>
+
+        <HStack>
+          <Button
+            colorScheme="blue"
+            onClick={handleSave}
+            isLoading={saving}
+            loadingText="Saving..."
+          >
+            {formData.storageResourceId ? "Update" : "Create"}
+          </Button>
+          {formData.storageResourceId && (
+            <Button colorScheme="red" onClick={handleDelete} isLoading={saving}>
+              Delete
+            </Button>
+          )}
+          <Button onClick={() => onSelect("")}>Back to List</Button>
+        </HStack>
+      </VStack>
+    </Box>
+  );
+};
+
diff --git a/airavata-research-portal/src/interfaces/ComputeResourceType.ts b/airavata-research-portal/src/interfaces/ComputeResourceType.ts
new file mode 100644
index 000000000..3fdef159e
--- /dev/null
+++ b/airavata-research-portal/src/interfaces/ComputeResourceType.ts
@@ -0,0 +1,42 @@
+export interface BatchQueue {
+  queueName: string;
+  queueDescription?: string;
+  maxRunTime?: number;
+  maxNodes?: number;
+  maxProcessors?: number;
+  maxJobsInQueue?: number;
+  maxMemory?: number;
+  cpuPerNode?: number;
+  defaultNodeCount?: number;
+  defaultCPUCount?: number;
+  defaultWalltime?: number;
+  queueSpecificMacros?: string;
+  isDefaultQueue?: boolean;
+}
+
+export interface ComputeResource {
+  computeResourceId?: string;
+  hostName: string;
+  hostAliases?: string[];
+  ipAddresses?: string[];
+  resourceDescription?: string;
+  enabled?: boolean;
+  batchQueues?: BatchQueue[];
+  fileSystems?: Record<string, string>;
+  maxMemoryPerNode?: number;
+  gatewayUsageReporting?: boolean;
+  gatewayUsageModuleLoadCommand?: string;
+  gatewayUsageExecutable?: string;
+  cpusPerNode?: number;
+  defaultNodeCount?: number;
+  defaultCPUCount?: number;
+  defaultWalltime?: number;
+}
+
+export interface ResourceName {
+  id: string;
+  name: string;
+}
+
+
+
diff --git a/airavata-research-portal/src/interfaces/StorageResourceType.ts b/airavata-research-portal/src/interfaces/StorageResourceType.ts
new file mode 100644
index 000000000..f6758bc78
--- /dev/null
+++ b/airavata-research-portal/src/interfaces/StorageResourceType.ts
@@ -0,0 +1,11 @@
+export interface StorageResource {
+  storageResourceId?: string;
+  hostName: string;
+  storageResourceDescription?: string;
+  enabled?: boolean;
+  creationTime?: number;
+  updateTime?: number;
+}
+
+
+
diff --git a/airavata-research-portal/src/lib/resourceApi.ts b/airavata-research-portal/src/lib/resourceApi.ts
new file mode 100644
index 000000000..9acc6e369
--- /dev/null
+++ b/airavata-research-portal/src/lib/resourceApi.ts
@@ -0,0 +1,109 @@
+import axios, { AxiosInstance } from 'axios';
+import { ComputeResource, BatchQueue, ResourceName } from '../interfaces/ComputeResourceType';
+import { StorageResource } from '../interfaces/StorageResourceType';
+
+const REST_API_URL = import.meta.env.VITE_REST_API_URL || 'http://localhost:8080';
+
+const resourceApi: AxiosInstance = axios.create({
+  baseURL: `${REST_API_URL}/api/v1`,
+  timeout: 30000,
+  headers: {
+    'Content-Type': 'application/json',
+  },
+});
+
+// Request interceptor for adding auth if needed
+resourceApi.interceptors.request.use(
+  (config) => {
+    // Add auth headers if available
+    const token = localStorage.getItem('access_token');
+    if (token) {
+      config.headers.Authorization = `Bearer ${token}`;
+    }
+    return config;
+  },
+  (error) => Promise.reject(error)
+);
+
+// Response interceptor
+resourceApi.interceptors.response.use(
+  (response) => response,
+  (error) => {
+    console.error('Resource API Error:', error.response?.data || error.message);
+    return Promise.reject(error);
+  }
+);
+
+// Compute Resource APIs
+export const computeResourceApi = {
+  getAll: async (): Promise<ResourceName[]> => {
+    const response = await resourceApi.get('/compute-resources');
+    return response.data;
+  },
+
+  get: async (id: string): Promise<ComputeResource> => {
+    const response = await resourceApi.get(`/compute-resources/${id}`);
+    return response.data;
+  },
+
+  create: async (resource: ComputeResource): Promise<{ computeResourceId: string }> => {
+    const response = await resourceApi.post('/compute-resources', resource);
+    return response.data;
+  },
+
+  update: async (id: string, resource: ComputeResource): Promise<void> => {
+    await resourceApi.put(`/compute-resources/${id}`, resource);
+  },
+
+  delete: async (id: string): Promise<void> => {
+    await resourceApi.delete(`/compute-resources/${id}`);
+  },
+
+  getQueues: async (id: string): Promise<BatchQueue[]> => {
+    const response = await resourceApi.get(`/compute-resources/${id}/queues`);
+    return response.data || [];
+  },
+
+  addQueue: async (id: string, queue: BatchQueue): Promise<void> => {
+    await resourceApi.post(`/compute-resources/${id}/queues`, queue);
+  },
+
+  updateQueue: async (id: string, queueName: string, queue: BatchQueue): Promise<void> => {
+    await resourceApi.put(`/compute-resources/${id}/queues/${queueName}`, queue);
+  },
+
+  deleteQueue: async (id: string, queueName: string): Promise<void> => {
+    await resourceApi.delete(`/compute-resources/${id}/queues/${queueName}`);
+  },
+};
+
+// Storage Resource APIs
+export const storageResourceApi = {
+  getAll: async (): Promise<ResourceName[]> => {
+    const response = await resourceApi.get('/storage-resources');
+    return response.data;
+  },
+
+  get: async (id: string): Promise<StorageResource> => {
+    const response = await resourceApi.get(`/storage-resources/${id}`);
+    return response.data;
+  },
+
+  create: async (resource: StorageResource): Promise<{ storageResourceId: string }> => {
+    const response = await resourceApi.post('/storage-resources', resource);
+    return response.data;
+  },
+
+  update: async (id: string, resource: StorageResource): Promise<void> => {
+    await resourceApi.put(`/storage-resources/${id}`, resource);
+  },
+
+  delete: async (id: string): Promise<void> => {
+    await resourceApi.delete(`/storage-resources/${id}`);
+  },
+};
+
+export default resourceApi;
+
+
+