UI/UX improvements (#218)
This commit is contained in:
@@ -23,7 +23,7 @@ const CardHeader = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
className={cn("flex flex-col space-y-1.5 p-4", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
@@ -57,7 +57,7 @@ const CardContent = React.forwardRef<
|
|||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
<div ref={ref} className={cn("p-4", className)} {...props} />
|
||||||
));
|
));
|
||||||
CardContent.displayName = "CardContent";
|
CardContent.displayName = "CardContent";
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ const CardFooter = React.forwardRef<
|
|||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex items-center p-6 pt-0", className)}
|
className={cn("flex items-center p-4 pt-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { useId } from "react";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -8,40 +7,54 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { useSettingsStore } from "@/store/SettingsStore";
|
import { useSettingsStore } from "@/store/SettingsStore";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
|
||||||
function Settings() {
|
function Settings() {
|
||||||
const { environment, organization, setEnvironment, setOrganization } =
|
const { environment, organization, setEnvironment, setOrganization } =
|
||||||
useSettingsStore();
|
useSettingsStore();
|
||||||
const environmentInputId = useId();
|
|
||||||
const organizationInputId = useId();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-8 max-w-5xl mx-auto">
|
||||||
<h1>Settings</h1>
|
<Card>
|
||||||
<div className="flex flex-col gap-4">
|
<CardHeader className="border-b-2">
|
||||||
<Label htmlFor={environmentInputId}>Environment</Label>
|
<CardTitle className="text-lg">Settings</CardTitle>
|
||||||
<div>
|
<CardDescription>
|
||||||
<Select value={environment} onValueChange={setEnvironment}>
|
You can select environment and organization here
|
||||||
<SelectTrigger>
|
</CardDescription>
|
||||||
<SelectValue placeholder="Environment" />
|
</CardHeader>
|
||||||
</SelectTrigger>
|
<CardContent className="p-8">
|
||||||
<SelectContent>
|
<div className="flex flex-col gap-4">
|
||||||
<SelectItem value="local">local</SelectItem>
|
<div className="flex gap-4 items-center">
|
||||||
</SelectContent>
|
<Label className="whitespace-nowrap w-36">Environment</Label>
|
||||||
</Select>
|
<Select value={environment} onValueChange={setEnvironment}>
|
||||||
</div>
|
<SelectTrigger>
|
||||||
<Label htmlFor={organizationInputId}>Organization</Label>
|
<SelectValue placeholder="Environment" />
|
||||||
<div>
|
</SelectTrigger>
|
||||||
<Select value={organization} onValueChange={setOrganization}>
|
<SelectContent>
|
||||||
<SelectTrigger>
|
<SelectItem value="local">local</SelectItem>
|
||||||
<SelectValue placeholder="Organization" />
|
</SelectContent>
|
||||||
</SelectTrigger>
|
</Select>
|
||||||
<SelectContent>
|
</div>
|
||||||
<SelectItem value="skyvern">Skyvern</SelectItem>
|
<div className="flex gap-4 items-center">
|
||||||
</SelectContent>
|
<Label className="whitespace-nowrap w-36">Organization</Label>
|
||||||
</Select>
|
<Select value={organization} onValueChange={setOrganization}>
|
||||||
</div>
|
<SelectTrigger>
|
||||||
</div>
|
<SelectValue placeholder="Organization" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="skyvern">Skyvern</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { StepArtifactsLayout } from "./StepArtifactsLayout";
|
|||||||
import Zoom from "react-medium-image-zoom";
|
import Zoom from "react-medium-image-zoom";
|
||||||
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
import { AspectRatio } from "@/components/ui/aspect-ratio";
|
||||||
import { getRecordingURL, getScreenshotURL } from "./artifactUtils";
|
import { getRecordingURL, getScreenshotURL } from "./artifactUtils";
|
||||||
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
|
|
||||||
function TaskDetails() {
|
function TaskDetails() {
|
||||||
const { taskId } = useParams();
|
const { taskId } = useParams();
|
||||||
@@ -39,14 +40,6 @@ function TaskDetails() {
|
|||||||
return <div>Error: {taskError?.message}</div>;
|
return <div>Error: {taskError?.message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTaskFetching) {
|
|
||||||
return <div>Loading...</div>; // TODO: skeleton
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!task) {
|
|
||||||
return <div>Task not found</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex flex-col gap-4 relative">
|
<div className="flex flex-col gap-4 relative">
|
||||||
@@ -58,9 +51,9 @@ function TaskDetails() {
|
|||||||
refetch();
|
refetch();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ReloadIcon />
|
<ReloadIcon className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
{task.recording_url ? (
|
{task?.recording_url ? (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Label className="w-32">Recording</Label>
|
<Label className="w-32">Recording</Label>
|
||||||
<video src={getRecordingURL(task)} controls />
|
<video src={getRecordingURL(task)} controls />
|
||||||
@@ -68,9 +61,13 @@ function TaskDetails() {
|
|||||||
) : null}
|
) : null}
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Label className="w-32">Status</Label>
|
<Label className="w-32">Status</Label>
|
||||||
<StatusBadge status={task.status} />
|
{isTaskFetching ? (
|
||||||
|
<Skeleton className="w-32 h-8" />
|
||||||
|
) : task ? (
|
||||||
|
<StatusBadge status={task?.status} />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{task.status === Status.Completed ? (
|
{task?.status === Status.Completed ? (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Label className="w-32 shrink-0">Extracted Information</Label>
|
<Label className="w-32 shrink-0">Extracted Information</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -80,7 +77,8 @@ function TaskDetails() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{task.status === Status.Failed || task.status === Status.Terminated ? (
|
{task?.status === Status.Failed ||
|
||||||
|
task?.status === Status.Terminated ? (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Label className="w-32 shrink-0">Failure Reason</Label>
|
<Label className="w-32 shrink-0">Failure Reason</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@@ -97,55 +95,59 @@ function TaskDetails() {
|
|||||||
<h1>Task Parameters</h1>
|
<h1>Task Parameters</h1>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div>
|
{task ? (
|
||||||
<p className="py-2">Task ID: {taskId}</p>
|
<div>
|
||||||
<p className="py-2">URL: {task.request.url}</p>
|
<p className="py-2">Task ID: {taskId}</p>
|
||||||
<p className="py-2">
|
<p className="py-2">URL: {task.request.url}</p>
|
||||||
Created: {basicTimeFormat(task.created_at)}
|
<p className="py-2">
|
||||||
</p>
|
Created: {basicTimeFormat(task.created_at)}
|
||||||
<div className="py-2">
|
</p>
|
||||||
<Label>Navigation Goal</Label>
|
<div className="py-2">
|
||||||
<Textarea
|
<Label>Navigation Goal</Label>
|
||||||
rows={5}
|
<Textarea
|
||||||
value={task.request.navigation_goal}
|
rows={5}
|
||||||
readOnly
|
value={task.request.navigation_goal}
|
||||||
/>
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="py-2">
|
||||||
|
<Label>Navigation Payload</Label>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
value={task.request.navigation_payload}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="py-2">
|
||||||
|
<Label>Data Extraction Goal</Label>
|
||||||
|
<Textarea
|
||||||
|
rows={5}
|
||||||
|
value={task.request.data_extraction_goal}
|
||||||
|
readOnly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="py-2">
|
) : null}
|
||||||
<Label>Navigation Payload</Label>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
value={task.request.navigation_payload}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="py-2">
|
|
||||||
<Label>Data Extraction Goal</Label>
|
|
||||||
<Textarea
|
|
||||||
rows={5}
|
|
||||||
value={task.request.data_extraction_goal}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="task-artifacts">
|
<AccordionItem value="task-artifacts">
|
||||||
<AccordionTrigger>
|
<AccordionTrigger>
|
||||||
<h1>Screenshot</h1>
|
<h1>Final Screenshot</h1>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent>
|
<AccordionContent>
|
||||||
<div className="max-w-sm mx-auto">
|
{task ? (
|
||||||
{task.screenshot_url ? (
|
<div className="max-w-sm mx-auto">
|
||||||
<Zoom zoomMargin={16}>
|
{task.screenshot_url ? (
|
||||||
<AspectRatio ratio={16 / 9}>
|
<Zoom zoomMargin={16}>
|
||||||
<img src={getScreenshotURL(task)} alt="screenshot" />
|
<AspectRatio ratio={16 / 9}>
|
||||||
</AspectRatio>
|
<img src={getScreenshotURL(task)} alt="screenshot" />
|
||||||
</Zoom>
|
</AspectRatio>
|
||||||
) : (
|
</Zoom>
|
||||||
<p>No screenshot</p>
|
) : (
|
||||||
)}
|
<p>No screenshot</p>
|
||||||
</div>
|
)}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</AccordionContent>
|
</AccordionContent>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
<AccordionItem value="task-steps">
|
<AccordionItem value="task-steps">
|
||||||
|
|||||||
@@ -25,6 +25,13 @@ import { PAGE_SIZE } from "../constants";
|
|||||||
import { StatusBadge } from "@/components/StatusBadge";
|
import { StatusBadge } from "@/components/StatusBadge";
|
||||||
import { basicTimeFormat } from "@/util/timeFormat";
|
import { basicTimeFormat } from "@/util/timeFormat";
|
||||||
import { QueuedTasks } from "../running/QueuedTasks";
|
import { QueuedTasks } from "../running/QueuedTasks";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
|
||||||
function TaskList() {
|
function TaskList() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -52,19 +59,11 @@ function TaskList() {
|
|||||||
placeholderData: keepPreviousData,
|
placeholderData: keepPreviousData,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isPending) {
|
|
||||||
return <TaskListSkeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isError) {
|
if (isError) {
|
||||||
return <div>Error: {error?.message}</div>;
|
return <div>Error: {error?.message}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tasks) {
|
const resolvedTasks = tasks?.filter(
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvedTasks = tasks.filter(
|
|
||||||
(task) =>
|
(task) =>
|
||||||
task.status === "completed" ||
|
task.status === "completed" ||
|
||||||
task.status === "failed" ||
|
task.status === "failed" ||
|
||||||
@@ -72,81 +71,110 @@ function TaskList() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-8 max-w-5xl mx-auto">
|
||||||
<h1 className="text-2xl py-2 border-b-2">Running Tasks</h1>
|
<Card>
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<CardHeader className="border-b-2">
|
||||||
<RunningTasks />
|
<CardTitle className="text-xl">Running Tasks</CardTitle>
|
||||||
</div>
|
<CardDescription>Tasks that are currently running</CardDescription>
|
||||||
<h1 className="text-2xl py-2 border-b-2">Queued Tasks</h1>
|
</CardHeader>
|
||||||
<QueuedTasks />
|
<CardContent className="p-4">
|
||||||
<h1 className="text-2xl py-2 border-b-2">Task History</h1>
|
<div className="grid grid-cols-4 gap-4">
|
||||||
<Table>
|
<RunningTasks />
|
||||||
<TableHeader>
|
</div>
|
||||||
<TableRow>
|
</CardContent>
|
||||||
<TableHead className="w-1/3">URL</TableHead>
|
</Card>
|
||||||
<TableHead className="w-1/3">Status</TableHead>
|
<Card>
|
||||||
<TableHead className="w-1/3">Created At</TableHead>
|
<CardHeader className="border-b-2">
|
||||||
</TableRow>
|
<CardTitle className="text-xl">Queued Tasks</CardTitle>
|
||||||
</TableHeader>
|
<CardDescription>Tasks that are waiting to run</CardDescription>
|
||||||
<TableBody>
|
</CardHeader>
|
||||||
{tasks.length === 0 ? (
|
<CardContent className="p-4">
|
||||||
<TableRow>
|
<QueuedTasks />
|
||||||
<TableCell colSpan={3}>No tasks found</TableCell>
|
</CardContent>
|
||||||
</TableRow>
|
</Card>
|
||||||
|
<Card>
|
||||||
|
<CardHeader className="border-b-2">
|
||||||
|
<CardTitle className="text-xl">Task History</CardTitle>
|
||||||
|
<CardDescription>Tasks you have run previously</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-4">
|
||||||
|
{isPending ? (
|
||||||
|
<TaskListSkeleton />
|
||||||
) : (
|
) : (
|
||||||
resolvedTasks.map((task) => {
|
<>
|
||||||
return (
|
<Table>
|
||||||
<TableRow
|
<TableHeader>
|
||||||
key={task.task_id}
|
<TableRow>
|
||||||
className="cursor-pointer w-4"
|
<TableHead className="w-1/3">URL</TableHead>
|
||||||
onClick={() => {
|
<TableHead className="w-1/3">Status</TableHead>
|
||||||
navigate(task.task_id);
|
<TableHead className="w-1/3">Created At</TableHead>
|
||||||
}}
|
</TableRow>
|
||||||
>
|
</TableHeader>
|
||||||
<TableCell className="w-1/3">{task.request.url}</TableCell>
|
<TableBody>
|
||||||
<TableCell className="w-1/3">
|
{tasks.length === 0 ? (
|
||||||
<StatusBadge status={task.status} />
|
<TableRow>
|
||||||
</TableCell>
|
<TableCell colSpan={3}>No tasks found</TableCell>
|
||||||
<TableCell className="w-1/3">
|
</TableRow>
|
||||||
{basicTimeFormat(task.created_at)}
|
) : (
|
||||||
</TableCell>
|
resolvedTasks?.map((task) => {
|
||||||
</TableRow>
|
return (
|
||||||
);
|
<TableRow
|
||||||
})
|
key={task.task_id}
|
||||||
|
className="cursor-pointer w-4"
|
||||||
|
onClick={() => {
|
||||||
|
navigate(task.task_id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell className="w-1/3">
|
||||||
|
{task.request.url}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="w-1/3">
|
||||||
|
<StatusBadge status={task.status} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="w-1/3">
|
||||||
|
{basicTimeFormat(task.created_at)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
<Pagination>
|
||||||
|
<PaginationContent>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationPrevious
|
||||||
|
href="#"
|
||||||
|
className={cn({ "cursor-not-allowed": page === 1 })}
|
||||||
|
onClick={() => {
|
||||||
|
if (page === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set("page", String(Math.max(1, page - 1)));
|
||||||
|
setSearchParams(params);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationLink href="#">{page}</PaginationLink>
|
||||||
|
</PaginationItem>
|
||||||
|
<PaginationItem>
|
||||||
|
<PaginationNext
|
||||||
|
href="#"
|
||||||
|
onClick={() => {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.set("page", String(page + 1));
|
||||||
|
setSearchParams(params);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PaginationItem>
|
||||||
|
</PaginationContent>
|
||||||
|
</Pagination>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</CardContent>
|
||||||
</Table>
|
</Card>
|
||||||
<Pagination>
|
|
||||||
<PaginationContent>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationPrevious
|
|
||||||
href="#"
|
|
||||||
className={cn({ "cursor-not-allowed": page === 1 })}
|
|
||||||
onClick={() => {
|
|
||||||
if (page === 1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.set("page", String(Math.max(1, page - 1)));
|
|
||||||
setSearchParams(params);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationLink href="#">{page}</PaginationLink>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext
|
|
||||||
href="#"
|
|
||||||
onClick={() => {
|
|
||||||
const params = new URLSearchParams();
|
|
||||||
params.set("page", String(page + 1));
|
|
||||||
setSearchParams(params);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table";
|
} from "@/components/ui/table";
|
||||||
|
|
||||||
const pageSizeArray = new Array(15).fill(null); // doesn't matter the value
|
const pageSizeArray = new Array(5).fill(null); // doesn't matter the value
|
||||||
|
|
||||||
function TaskListSkeleton() {
|
function TaskListSkeleton() {
|
||||||
return (
|
return (
|
||||||
@@ -26,13 +26,13 @@ function TaskListSkeleton() {
|
|||||||
return (
|
return (
|
||||||
<TableRow key={index}>
|
<TableRow key={index}>
|
||||||
<TableCell className="w-1/3">
|
<TableCell className="w-1/3">
|
||||||
<Skeleton className="w-full h-full" />
|
<Skeleton className="w-full h-4" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="w-1/3">
|
<TableCell className="w-1/3">
|
||||||
<Skeleton className="w-full h-full" />
|
<Skeleton className="w-full h-4" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="w-1/3">
|
<TableCell className="w-1/3">
|
||||||
<Skeleton className="w-full h-full" />
|
<Skeleton className="w-full h-4" />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ function QueuedTasks() {
|
|||||||
?.filter((task) => task.status === Status.Queued)
|
?.filter((task) => task.status === Status.Queued)
|
||||||
.slice(0, 10);
|
.slice(0, 10);
|
||||||
|
|
||||||
|
if (queuedTasks?.length === 0) {
|
||||||
|
return <div>No queued tasks</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
|
|||||||
Reference in New Issue
Block a user