This is an automated email from the ASF dual-hosted git repository.
yasith pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
The following commit(s) were added to refs/heads/main by this push:
new 87912a6be Frontend support for deleting all sessions button
87912a6be is described below
commit 87912a6be8ea4c0811c19af07fc899b0b1e84817
Author: ganning127 <[email protected]>
AuthorDate: Fri Aug 1 00:03:17 2025 -0700
Frontend support for deleting all sessions button
---
.../src/components/home/DeleteAllSessions.tsx | 86 +++++++++++
.../src/components/home/SessionCard.tsx | 170 ++++++++++-----------
.../src/components/home/SessionsSection.tsx | 94 ++++++------
3 files changed, 221 insertions(+), 129 deletions(-)
diff --git a/airavata-research-portal/src/components/home/DeleteAllSessions.tsx
b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx
new file mode 100644
index 000000000..e9cfe9038
--- /dev/null
+++ b/airavata-research-portal/src/components/home/DeleteAllSessions.tsx
@@ -0,0 +1,86 @@
+import {SessionType} from "@/interfaces/SessionType.tsx";
+import {Button, CloseButton, Dialog, Input, Portal, Text, useDialog} from
"@chakra-ui/react";
+import {useState} from "react";
+import {toaster} from "@/components/ui/toaster.tsx";
+import {CONTROLLER} from "@/lib/controller.ts";
+import api from "@/lib/api.ts";
+
+export const DeleteAllSessions = ({
+ sessions,
+ setSessions
+ }: {
+ sessions: SessionType[],
+ setSessions: (sessions: SessionType[]) => void
+}) => {
+ const dialog = useDialog();
+ const [loading, setLoading] = useState(false);
+ const [deleteName, setDeleteName] = useState("");
+ const handleDelete = async () => {
+ setLoading(true);
+
+ try {
+ const ids = sessions.map(s => s.id);
+ await api.delete(`${CONTROLLER.sessions}/delete/${ids.join(",")}`);
+ setSessions([]);
+ } catch {
+ toaster.create({
+ 'title': 'Error',
+ 'description': 'Failed to delete all sessions',
+ 'type': 'error'
+ })
+ } finally {
+ setLoading(false);
+ }
+ }
+ return (
+ <>
+ <Dialog.RootProvider size="sm" value={dialog}>
+ <Portal>
+ <Dialog.Backdrop/>
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Header>
+ <Dialog.Title>Delete All Sessions</Dialog.Title>
+ </Dialog.Header>
+ <Dialog.Body>
+ <Text color="gray.500">
+ This action is irreversible. To confirm, please type:{" "}
+ <b>delete all sessions</b>.
+ </Text>
+
+ <Input
+ mt={2}
+ placeholder="Session name"
+ value={deleteName}
+ onChange={(e) => setDeleteName(e.target.value)}
+ />
+ </Dialog.Body>
+ <Dialog.Footer>
+ <Button
+ width="100%"
+ colorPalette="red"
+ disabled={deleteName !== "delete all sessions" ||
loading}
+ loading={loading}
+ onClick={handleDelete}
+ >
+ Delete
+ </Button>
+ </Dialog.Footer>
+ <Dialog.CloseTrigger asChild>
+ <CloseButton size="sm"/>
+ </Dialog.CloseTrigger>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.RootProvider>
+
+ <Button
+ colorPalette={'red'}
+ loading={loading}
+ onClick={() => dialog.setOpen(true)}
+ >
+ Delete All
+ </Button>
+ </>
+ )
+}
\ No newline at end of file
diff --git a/airavata-research-portal/src/components/home/SessionCard.tsx
b/airavata-research-portal/src/components/home/SessionCard.tsx
index 2bd14a2d9..8f9add93a 100644
--- a/airavata-research-portal/src/components/home/SessionCard.tsx
+++ b/airavata-research-portal/src/components/home/SessionCard.tsx
@@ -1,26 +1,26 @@
-import { SessionType } from "@/interfaces/SessionType";
+import {SessionType} from "@/interfaces/SessionType";
import {
+ Badge,
Box,
- Text,
+ Button,
Card,
+ CloseButton,
+ Dialog,
Heading,
HStack,
- Badge,
- VStack,
IconButton,
- Dialog,
- useDialog,
- CloseButton,
- Portal,
- Button,
Input,
+ Portal,
+ Text,
+ useDialog,
+ VStack,
} from "@chakra-ui/react";
-import { SessionCardControls } from "./SessionCardControls";
-import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
-import { FaTrash } from "react-icons/fa";
-import { toaster } from "../ui/toaster";
-import { CONTROLLER } from "@/lib/controller";
-import { useState } from "react";
+import {SessionCardControls} from "./SessionCardControls";
+import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum";
+import {FaTrash} from "react-icons/fa";
+import {toaster} from "../ui/toaster";
+import {CONTROLLER} from "@/lib/controller";
+import {useState} from "react";
import api from "@/lib/api";
const getColorPalette = (status: string) => {
@@ -40,7 +40,7 @@ const getColorPalette = (status: string) => {
}
};
-export const SessionCard = ({ session }: { session: SessionType }) => {
+export const SessionCard = ({session}: { session: SessionType }) => {
const dialog = useDialog();
const [hideCard, setHideCard] = useState(false);
const [deleteName, setDeleteName] = useState("");
@@ -68,81 +68,79 @@ export const SessionCard = ({ session }: { session:
SessionType }) => {
};
return (
- <>
- <Dialog.RootProvider size="sm" value={dialog}>
- <Portal>
- <Dialog.Backdrop />
- <Dialog.Positioner>
- <Dialog.Content>
- <Dialog.Header>
- <Dialog.Title>Delete Session</Dialog.Title>
- </Dialog.Header>
- <Dialog.Body>
- <Text color="gray.500">
- This action is irreversible. To confirm, please type:{" "}
- <b>{session.sessionName}</b>.
- </Text>
+ <>
+ <Dialog.RootProvider size="sm" value={dialog}>
+ <Portal>
+ <Dialog.Backdrop/>
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Header>
+ <Dialog.Title>Delete Session</Dialog.Title>
+ </Dialog.Header>
+ <Dialog.Body>
+ <Text color="gray.500">
+ This action is irreversible. To confirm, please type:{" "}
+ <b>{session.sessionName}</b>.
+ </Text>
- <Input
- mt={2}
- placeholder="Session name"
- value={deleteName}
- onChange={(e) => setDeleteName(e.target.value)}
- />
- </Dialog.Body>
- <Dialog.Footer>
- <Button
- width="100%"
- colorPalette="red"
- disabled={deleteName !== session.sessionName ||
deleteLoading}
- loading={deleteLoading}
- onClick={handleDeleteSession}
- >
- Delete
- </Button>
- </Dialog.Footer>
- <Dialog.CloseTrigger asChild>
- <CloseButton size="sm" />
- </Dialog.CloseTrigger>
- </Dialog.Content>
- </Dialog.Positioner>
- </Portal>
- </Dialog.RootProvider>
+ <Input
+ mt={2}
+ placeholder="Session name"
+ value={deleteName}
+ onChange={(e) => setDeleteName(e.target.value)}
+ />
+ </Dialog.Body>
+ <Dialog.Footer>
+ <Button
+ width="100%"
+ colorPalette="red"
+ disabled={deleteName !== session.sessionName ||
deleteLoading}
+ loading={deleteLoading}
+ onClick={handleDeleteSession}
+ >
+ Delete
+ </Button>
+ </Dialog.Footer>
+ <Dialog.CloseTrigger asChild>
+ <CloseButton size="sm"/>
+ </Dialog.CloseTrigger>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.RootProvider>
- <Card.Root size="sm" height="fit-content" hidden={hideCard}>
- <Card.Header>
- <HStack justify="space-between" alignItems="flex-start">
- <Box>
- <Heading size="lg">{session.sessionName}</Heading>
- </Box>
- <VStack alignItems="flex-end">
- {session.status === SessionStatusEnum.TERMINATED && (
+ <Card.Root size="sm" height="fit-content" hidden={hideCard}>
+ <Card.Header>
+ <HStack justify="space-between" alignItems="flex-start">
+ <Box>
+ <Heading size="lg">{session.sessionName}</Heading>
+ </Box>
+ <VStack alignItems="flex-end">
<IconButton
- color="red.600"
- size="xs"
- variant={"ghost"}
- onClick={() => dialog.setOpen(true)}
+ color="red.600"
+ size="xs"
+ variant={"ghost"}
+ onClick={() => dialog.setOpen(true)}
>
- <FaTrash />
+ <FaTrash/>
</IconButton>
- )}
- </VStack>
- </HStack>
- </Card.Header>
- <Card.Body>
- <HStack alignItems={"center"} gap={1}>
- <Badge size="md" colorPalette={getColorPalette(session.status)}>
- {session.status}
- </Badge>
- •
- <Text color="fg.muted">
- {new Date(session.createdAt).toLocaleString()}
- </Text>
- </HStack>
+ </VStack>
+ </HStack>
+ </Card.Header>
+ <Card.Body>
+ <HStack alignItems={"center"} gap={1}>
+ <Badge size="md" colorPalette={getColorPalette(session.status)}>
+ {session.status}
+ </Badge>
+ •
+ <Text color="fg.muted">
+ {new Date(session.createdAt).toLocaleString()}
+ </Text>
+ </HStack>
- <SessionCardControls session={session} />
- </Card.Body>
- </Card.Root>
- </>
+ <SessionCardControls session={session}/>
+ </Card.Body>
+ </Card.Root>
+ </>
);
};
diff --git a/airavata-research-portal/src/components/home/SessionsSection.tsx
b/airavata-research-portal/src/components/home/SessionsSection.tsx
index 1b3de8aca..baf6c27ce 100644
--- a/airavata-research-portal/src/components/home/SessionsSection.tsx
+++ b/airavata-research-portal/src/components/home/SessionsSection.tsx
@@ -1,10 +1,11 @@
-import { SessionType } from "@/interfaces/SessionType";
-import { SessionCard } from "./SessionCard";
+import {SessionType} from "@/interfaces/SessionType";
+import {SessionCard} from "./SessionCard";
import api from "@/lib/api";
-import { useEffect, useState } from "react";
-import { CONTROLLER } from "@/lib/controller";
-import { Button, HStack, SimpleGrid, Text } from "@chakra-ui/react";
-import { SessionStatusEnum } from "@/interfaces/SessionStatusEnum";
+import {useEffect, useState} from "react";
+import {CONTROLLER} from "@/lib/controller";
+import {Button, HStack, SimpleGrid, Text} from "@chakra-ui/react";
+import {SessionStatusEnum} from "@/interfaces/SessionStatusEnum";
+import {DeleteAllSessions} from "@/components/home/DeleteAllSessions.tsx";
async function getSessions(status: SessionStatusEnum | null = null) {
try {
@@ -26,7 +27,7 @@ async function getSessions(status: SessionStatusEnum | null =
null) {
export const SessionsSection = () => {
const [sessionStatusFilter, setSessionStatusFilter] =
- useState<SessionStatusEnum | null>(null);
+ useState<SessionStatusEnum | null>(null);
const [sessions, setSessions] = useState<SessionType[]>([]);
useEffect(() => {
@@ -54,42 +55,49 @@ export const SessionsSection = () => {
];
return (
- <>
- <HStack flexWrap={"wrap"} gap={2} mt={4}>
- <Button
- variant={sessionStatusFilter === null ? "solid" : "outline"}
- size="sm"
- onClick={() => {
- setSessionStatusFilter(null);
- }}
- >
- All
- </Button>
- {/* {Object.values(SessionStatusEnum).map((status) => ( */}
- {sessionStatusFilterOptions.map((status) => (
- <Button
- key={status}
- variant={sessionStatusFilter === status ? "solid" : "outline"}
- size="sm"
- onClick={() => {
- setSessionStatusFilter(status);
- }}
- >
- {status}
- </Button>
- ))}
- </HStack>
- <SimpleGrid mt={4} columns={{ base: 1, md: 2, lg: 3 }} gap={4}>
- {sessions.map((session: SessionType) => {
- return <SessionCard key={session.id} session={session} />;
- })}
- </SimpleGrid>
+ <>
+ <HStack flexWrap={"wrap"} mt={4} justifyContent={'space-between'}>
+ <HStack flexWrap={"wrap"} gap={2}>
+ <Button
+ variant={sessionStatusFilter === null ? "solid" : "outline"}
+ size="sm"
+ onClick={() => {
+ setSessionStatusFilter(null);
+ }}
+ >
+ All
+ </Button>
+ {/* {Object.values(SessionStatusEnum).map((status) => ( */}
+ {sessionStatusFilterOptions.map((status) => (
+ <Button
+ key={status}
+ variant={sessionStatusFilter === status ? "solid" :
"outline"}
+ size="sm"
+ onClick={() => {
+ setSessionStatusFilter(status);
+ }}
+ >
+ {status}
+ </Button>
+ ))}
+ </HStack>
+ {
+ sessions.length > 0 && (
+ <DeleteAllSessions sessions={sessions}
setSessions={setSessions}/>
+ )
+ }
+ </HStack>
+ <SimpleGrid mt={4} columns={{base: 1, md: 2, lg: 3}} gap={4}>
+ {sessions.map((session: SessionType) => {
+ return <SessionCard key={session.id} session={session}/>;
+ })}
+ </SimpleGrid>
- {sessions.length === 0 && (
- <Text mt={4} color="gray.500">
- No sessions found.
- </Text>
- )}
- </>
+ {sessions.length === 0 && (
+ <Text mt={4} color="gray.500">
+ No sessions found.
+ </Text>
+ )}
+ </>
);
};