feat(web): paginated runs table, add loading page
This commit is contained in:
		
							parent
							
								
									744a222e26
								
							
						
					
					
						commit
						d63ab1924b
					
				
							
								
								
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/bun.lockb
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -66,6 +66,7 @@
 | 
				
			|||||||
    "next-plausible": "^3.12.0",
 | 
					    "next-plausible": "^3.12.0",
 | 
				
			||||||
    "next-themes": "^0.2.1",
 | 
					    "next-themes": "^0.2.1",
 | 
				
			||||||
    "next-usequerystate": "^1.13.2",
 | 
					    "next-usequerystate": "^1.13.2",
 | 
				
			||||||
 | 
					    "pg": "^8.11.3",
 | 
				
			||||||
    "react": "^18",
 | 
					    "react": "^18",
 | 
				
			||||||
    "react-day-picker": "^8.9.1",
 | 
					    "react-day-picker": "^8.9.1",
 | 
				
			||||||
    "react-dom": "^18",
 | 
					    "react-dom": "^18",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								web/src/app/(app)/api-keys/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/app/(app)/api-keys/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { usePathname } from "next/navigation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  const pathName = usePathname();
 | 
				
			||||||
 | 
					  return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										9
									
								
								web/src/app/(app)/machines/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/app/(app)/machines/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { usePathname } from "next/navigation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  const pathName = usePathname();
 | 
				
			||||||
 | 
					  return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					import { LoadingWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { DeploymentsTable } from "@/components/RunsTable";
 | 
				
			||||||
 | 
					import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Page({
 | 
				
			||||||
 | 
					  params,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  params: { workflow_id: string };
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const workflow_id = params.workflow_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card className="w-full h-fit">
 | 
				
			||||||
 | 
					      <CardHeader>
 | 
				
			||||||
 | 
					        <CardTitle>Deployments</CardTitle>
 | 
				
			||||||
 | 
					      </CardHeader>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <LoadingWrapper tag="deployments">
 | 
				
			||||||
 | 
					          <DeploymentsTable workflow_id={workflow_id} />
 | 
				
			||||||
 | 
					        </LoadingWrapper>
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								web/src/app/(app)/workflows/[workflow_id]/@runs/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								web/src/app/(app)/workflows/[workflow_id]/@runs/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import { LoadingWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { RunsTable } from "@/components/RunsTable";
 | 
				
			||||||
 | 
					import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Page({
 | 
				
			||||||
 | 
					  params,
 | 
				
			||||||
 | 
					  searchParams,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  params: { workflow_id: string };
 | 
				
			||||||
 | 
					  searchParams: { [key: string]: string | string[] | undefined };
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const workflow_id = params.workflow_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card className="w-full h-fit min-w-0">
 | 
				
			||||||
 | 
					      <CardHeader>
 | 
				
			||||||
 | 
					        <CardTitle>Run</CardTitle>
 | 
				
			||||||
 | 
					      </CardHeader>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <LoadingWrapper tag="runs">
 | 
				
			||||||
 | 
					          <RunsTable workflow_id={workflow_id} searchParams={searchParams} />
 | 
				
			||||||
 | 
					        </LoadingWrapper>
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { Card } from "@/components/ui/card";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  // You can add any UI inside Loading, including a Skeleton.
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card className="w-full h-fit">
 | 
				
			||||||
 | 
					      <LoadingPageWrapper tag="workflow details" />
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import { MachinesWSMain } from "@/components/MachinesWS";
 | 
				
			||||||
 | 
					import { VersionDetails } from "@/components/VersionDetails";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  CopyWorkflowVersion,
 | 
				
			||||||
 | 
					  CreateDeploymentButton,
 | 
				
			||||||
 | 
					  MachineSelect,
 | 
				
			||||||
 | 
					  RunWorkflowButton,
 | 
				
			||||||
 | 
					  VersionSelect,
 | 
				
			||||||
 | 
					  ViewWorkflowDetailsButton,
 | 
				
			||||||
 | 
					} from "@/components/VersionSelect";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Card,
 | 
				
			||||||
 | 
					  CardContent,
 | 
				
			||||||
 | 
					  CardDescription,
 | 
				
			||||||
 | 
					  CardHeader,
 | 
				
			||||||
 | 
					  CardTitle,
 | 
				
			||||||
 | 
					} from "@/components/ui/card";
 | 
				
			||||||
 | 
					import { getRelativeTime } from "@/lib/getRelativeTime";
 | 
				
			||||||
 | 
					import { getMachines } from "@/server/curdMachine";
 | 
				
			||||||
 | 
					import { findFirstTableWithVersion } from "@/server/findFirstTableWithVersion";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async function Page({
 | 
				
			||||||
 | 
					  params,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  params: { workflow_id: string };
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const workflow_id = params.workflow_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const workflow = await findFirstTableWithVersion(workflow_id);
 | 
				
			||||||
 | 
					  const machines = await getMachines();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card className="w-full h-fit">
 | 
				
			||||||
 | 
					      <CardHeader>
 | 
				
			||||||
 | 
					        <CardTitle>{workflow?.name}</CardTitle>
 | 
				
			||||||
 | 
					        <CardDescription suppressHydrationWarning={true}>
 | 
				
			||||||
 | 
					          {getRelativeTime(workflow?.updated_at)}
 | 
				
			||||||
 | 
					        </CardDescription>
 | 
				
			||||||
 | 
					      </CardHeader>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <CardContent>
 | 
				
			||||||
 | 
					        <div className="flex gap-2 flex-wrap">
 | 
				
			||||||
 | 
					          <VersionSelect workflow={workflow} />
 | 
				
			||||||
 | 
					          <MachineSelect machines={machines} />
 | 
				
			||||||
 | 
					          <RunWorkflowButton workflow={workflow} machines={machines} />
 | 
				
			||||||
 | 
					          <CreateDeploymentButton workflow={workflow} machines={machines} />
 | 
				
			||||||
 | 
					          <CopyWorkflowVersion workflow={workflow} />
 | 
				
			||||||
 | 
					          <ViewWorkflowDetailsButton workflow={workflow} />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <VersionDetails workflow={workflow} />
 | 
				
			||||||
 | 
					        <MachinesWSMain machines={machines} />
 | 
				
			||||||
 | 
					      </CardContent>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										22
									
								
								web/src/app/(app)/workflows/[workflow_id]/layout.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								web/src/app/(app)/workflows/[workflow_id]/layout.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					export default async function Layout({
 | 
				
			||||||
 | 
					  children,
 | 
				
			||||||
 | 
					  deployment,
 | 
				
			||||||
 | 
					  runs,
 | 
				
			||||||
 | 
					  workflow,
 | 
				
			||||||
 | 
					}: {
 | 
				
			||||||
 | 
					  children: React.ReactNode;
 | 
				
			||||||
 | 
					  deployment: React.ReactNode;
 | 
				
			||||||
 | 
					  runs: React.ReactNode;
 | 
				
			||||||
 | 
					  workflow: React.ReactNode;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className="mt-4 w-full grid grid-rows-[1fr,1fr] lg:grid-cols-[minmax(auto,500px),1fr] gap-4 max-h-[calc(100dvh-100px)]">
 | 
				
			||||||
 | 
					      <div className="w-full flex gap-4 flex-col min-w-0">
 | 
				
			||||||
 | 
					        {workflow}
 | 
				
			||||||
 | 
					        {deployment}
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      {runs}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										6
									
								
								web/src/app/(app)/workflows/[workflow_id]/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								web/src/app/(app)/workflows/[workflow_id]/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  // You can add any UI inside Loading, including a Skeleton.
 | 
				
			||||||
 | 
					  return <LoadingPageWrapper tag="workflow" />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,80 +0,0 @@
 | 
				
			|||||||
import { DeploymentsTable, RunsTable } from "../../../../components/RunsTable";
 | 
					 | 
				
			||||||
import { findFirstTableWithVersion } from "../../../../server/findFirstTableWithVersion";
 | 
					 | 
				
			||||||
import { MachinesWSMain } from "@/components/MachinesWS";
 | 
					 | 
				
			||||||
import { VersionDetails } from "@/components/VersionDetails";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  CopyWorkflowVersion,
 | 
					 | 
				
			||||||
  CreateDeploymentButton,
 | 
					 | 
				
			||||||
  MachineSelect,
 | 
					 | 
				
			||||||
  RunWorkflowButton,
 | 
					 | 
				
			||||||
  VersionSelect,
 | 
					 | 
				
			||||||
  ViewWorkflowDetailsButton,
 | 
					 | 
				
			||||||
} from "@/components/VersionSelect";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
  Card,
 | 
					 | 
				
			||||||
  CardContent,
 | 
					 | 
				
			||||||
  CardDescription,
 | 
					 | 
				
			||||||
  CardHeader,
 | 
					 | 
				
			||||||
  CardTitle,
 | 
					 | 
				
			||||||
} from "@/components/ui/card";
 | 
					 | 
				
			||||||
import { getRelativeTime } from "@/lib/getRelativeTime";
 | 
					 | 
				
			||||||
import { getMachines } from "@/server/curdMachine";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default async function Page({
 | 
					 | 
				
			||||||
  params,
 | 
					 | 
				
			||||||
}: {
 | 
					 | 
				
			||||||
  params: { workflow_id: string };
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  const workflow_id = params.workflow_id;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const workflow = await findFirstTableWithVersion(workflow_id);
 | 
					 | 
				
			||||||
  const machines = await getMachines();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className="mt-4 w-full flex flex-col lg:flex-row gap-4 max-h-[calc(100dvh-100px)]">
 | 
					 | 
				
			||||||
      <div className="flex gap-4 flex-col">
 | 
					 | 
				
			||||||
        <Card className="w-full lg:w-fit lg:min-w-[600px] h-fit">
 | 
					 | 
				
			||||||
          <CardHeader>
 | 
					 | 
				
			||||||
            <CardTitle>{workflow?.name}</CardTitle>
 | 
					 | 
				
			||||||
            <CardDescription suppressHydrationWarning={true}>
 | 
					 | 
				
			||||||
              {getRelativeTime(workflow?.updated_at)}
 | 
					 | 
				
			||||||
            </CardDescription>
 | 
					 | 
				
			||||||
          </CardHeader>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <CardContent>
 | 
					 | 
				
			||||||
            <div className="flex gap-2 flex-wrap">
 | 
					 | 
				
			||||||
              <VersionSelect workflow={workflow} />
 | 
					 | 
				
			||||||
              <MachineSelect machines={machines} />
 | 
					 | 
				
			||||||
              <RunWorkflowButton workflow={workflow} machines={machines} />
 | 
					 | 
				
			||||||
              <CreateDeploymentButton workflow={workflow} machines={machines} />
 | 
					 | 
				
			||||||
              <CopyWorkflowVersion workflow={workflow} />
 | 
					 | 
				
			||||||
              <ViewWorkflowDetailsButton workflow={workflow} />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <VersionDetails workflow={workflow} />
 | 
					 | 
				
			||||||
            <MachinesWSMain machines={machines} />
 | 
					 | 
				
			||||||
          </CardContent>
 | 
					 | 
				
			||||||
        </Card>
 | 
					 | 
				
			||||||
        <Card className="w-full h-fit">
 | 
					 | 
				
			||||||
          <CardHeader>
 | 
					 | 
				
			||||||
            <CardTitle>Deployments</CardTitle>
 | 
					 | 
				
			||||||
          </CardHeader>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          <CardContent>
 | 
					 | 
				
			||||||
            <DeploymentsTable workflow_id={workflow_id} />
 | 
					 | 
				
			||||||
          </CardContent>
 | 
					 | 
				
			||||||
        </Card>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      <Card className="w-full h-fit">
 | 
					 | 
				
			||||||
        <CardHeader>
 | 
					 | 
				
			||||||
          <CardTitle>Run</CardTitle>
 | 
					 | 
				
			||||||
        </CardHeader>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        <CardContent>
 | 
					 | 
				
			||||||
          <RunsTable workflow_id={workflow_id} />
 | 
					 | 
				
			||||||
        </CardContent>
 | 
					 | 
				
			||||||
      </Card>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										9
									
								
								web/src/app/(app)/workflows/loading.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								web/src/app/(app)/workflows/loading.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { LoadingPageWrapper } from "@/components/LoadingWrapper";
 | 
				
			||||||
 | 
					import { usePathname } from "next/navigation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Loading() {
 | 
				
			||||||
 | 
					  const pathName = usePathname();
 | 
				
			||||||
 | 
					  return <LoadingPageWrapper className="h-full" tag={pathName.toLowerCase()} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								web/src/components/LoadingWrapper.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								web/src/components/LoadingWrapper.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					import { LoadingIcon } from "@/components/LoadingIcon";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					import { Suspense } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function LoadingWrapper(props: {
 | 
				
			||||||
 | 
					  tag: string;
 | 
				
			||||||
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Suspense
 | 
				
			||||||
 | 
					      fallback={
 | 
				
			||||||
 | 
					        <div className="w-full py-4 flex justify-center items-center gap-2 text-sm">
 | 
				
			||||||
 | 
					          Fetching {props.tag} <LoadingIcon />
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      {props.children}
 | 
				
			||||||
 | 
					    </Suspense>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function LoadingPageWrapper(props: {
 | 
				
			||||||
 | 
					  tag: string;
 | 
				
			||||||
 | 
					  children?: React.ReactNode;
 | 
				
			||||||
 | 
					  className?: string;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        "w-full py-4 flex justify-center items-center gap-2 text-sm",
 | 
				
			||||||
 | 
					        props.className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      Fetching {props.tag} <LoadingIcon />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										42
									
								
								web/src/components/PaginationControl.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								web/src/components/PaginationControl.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					  Pagination,
 | 
				
			||||||
 | 
					  PaginationContent,
 | 
				
			||||||
 | 
					  PaginationEllipsis,
 | 
				
			||||||
 | 
					  PaginationItem,
 | 
				
			||||||
 | 
					  PaginationLink,
 | 
				
			||||||
 | 
					  PaginationNext,
 | 
				
			||||||
 | 
					  PaginationPrevious,
 | 
				
			||||||
 | 
					} from "@/components/ui/pagination";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function PaginationControl(props: {
 | 
				
			||||||
 | 
					  totalPage: number;
 | 
				
			||||||
 | 
					  currentPage: number;
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Pagination>
 | 
				
			||||||
 | 
					      <PaginationContent>
 | 
				
			||||||
 | 
					        <PaginationPrevious
 | 
				
			||||||
 | 
					          href={
 | 
				
			||||||
 | 
					            props.currentPage > 1
 | 
				
			||||||
 | 
					              ? `?page=${props.currentPage - 1}`
 | 
				
			||||||
 | 
					              : `?page=${props.currentPage}`
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <PaginationLink href="#" isActive>
 | 
				
			||||||
 | 
					          {props.currentPage}
 | 
				
			||||||
 | 
					        </PaginationLink>
 | 
				
			||||||
 | 
					        <PaginationItem>
 | 
				
			||||||
 | 
					          <PaginationEllipsis />
 | 
				
			||||||
 | 
					        </PaginationItem>
 | 
				
			||||||
 | 
					        <PaginationNext
 | 
				
			||||||
 | 
					          className="cursor-pointer"
 | 
				
			||||||
 | 
					          href={
 | 
				
			||||||
 | 
					            props.currentPage < props.totalPage
 | 
				
			||||||
 | 
					              ? `?page=${props.currentPage + 1}`
 | 
				
			||||||
 | 
					              : `?page=${props.totalPage}`
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </PaginationContent>
 | 
				
			||||||
 | 
					    </Pagination>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,5 +1,9 @@
 | 
				
			|||||||
import { findAllDeployments, findAllRuns } from "../server/findAllRuns";
 | 
					import {
 | 
				
			||||||
 | 
					  findAllDeployments,
 | 
				
			||||||
 | 
					  findAllRunsWithCounts,
 | 
				
			||||||
 | 
					} from "../server/findAllRuns";
 | 
				
			||||||
import { DeploymentDisplay } from "./DeploymentDisplay";
 | 
					import { DeploymentDisplay } from "./DeploymentDisplay";
 | 
				
			||||||
 | 
					import { PaginationControl } from "./PaginationControl";
 | 
				
			||||||
import { RunDisplay } from "./RunDisplay";
 | 
					import { RunDisplay } from "./RunDisplay";
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  Table,
 | 
					  Table,
 | 
				
			||||||
@ -9,29 +13,51 @@ import {
 | 
				
			|||||||
  TableHeader,
 | 
					  TableHeader,
 | 
				
			||||||
  TableRow,
 | 
					  TableRow,
 | 
				
			||||||
} from "@/components/ui/table";
 | 
					} from "@/components/ui/table";
 | 
				
			||||||
 | 
					import { parseAsInteger } from "next-usequerystate";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function RunsTable(props: { workflow_id: string }) {
 | 
					const itemPerPage = 4;
 | 
				
			||||||
  const allRuns = await findAllRuns(props.workflow_id);
 | 
					const pageParser = parseAsInteger.withDefault(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function RunsTable(props: {
 | 
				
			||||||
 | 
					  workflow_id: string;
 | 
				
			||||||
 | 
					  searchParams: { [key: string]: string | string[] | undefined };
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  // await new Promise((resolve) => setTimeout(resolve, 5000));
 | 
				
			||||||
 | 
					  const page = pageParser.parseServerSide(
 | 
				
			||||||
 | 
					    props.searchParams?.page ?? undefined
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  const { allRuns, total } = await findAllRunsWithCounts({
 | 
				
			||||||
 | 
					    workflow_id: props.workflow_id,
 | 
				
			||||||
 | 
					    limit: itemPerPage,
 | 
				
			||||||
 | 
					    offset: (page - 1) * itemPerPage,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div className="overflow-auto h-[400px] w-full">
 | 
					    <div>
 | 
				
			||||||
      <Table className="">
 | 
					      <div className="overflow-auto h-[400px] w-full">
 | 
				
			||||||
        <TableCaption>A list of your recent runs.</TableCaption>
 | 
					        <Table className="">
 | 
				
			||||||
        <TableHeader className="bg-background top-0 sticky">
 | 
					          {/* <TableCaption>A list of your recent runs.</TableCaption> */}
 | 
				
			||||||
          <TableRow>
 | 
					          <TableHeader className="bg-background top-0 sticky">
 | 
				
			||||||
            <TableHead className="w-[100px]">Number</TableHead>
 | 
					            <TableRow>
 | 
				
			||||||
            <TableHead className="">Machine</TableHead>
 | 
					              <TableHead className="w-[100px]">Number</TableHead>
 | 
				
			||||||
            <TableHead className="">Time</TableHead>
 | 
					              <TableHead className="">Machine</TableHead>
 | 
				
			||||||
            <TableHead className="w-[100px]">Version</TableHead>
 | 
					              <TableHead className="">Time</TableHead>
 | 
				
			||||||
            <TableHead className="">Live Status</TableHead>
 | 
					              <TableHead className="w-[100px]">Version</TableHead>
 | 
				
			||||||
            <TableHead className=" text-right">Status</TableHead>
 | 
					              <TableHead className="">Live Status</TableHead>
 | 
				
			||||||
          </TableRow>
 | 
					              <TableHead className=" text-right">Status</TableHead>
 | 
				
			||||||
        </TableHeader>
 | 
					            </TableRow>
 | 
				
			||||||
        <TableBody>
 | 
					          </TableHeader>
 | 
				
			||||||
          {allRuns.map((run) => (
 | 
					          <TableBody>
 | 
				
			||||||
            <RunDisplay run={run} key={run.id} />
 | 
					            {allRuns.map((run) => (
 | 
				
			||||||
          ))}
 | 
					              <RunDisplay run={run} key={run.id} />
 | 
				
			||||||
        </TableBody>
 | 
					            ))}
 | 
				
			||||||
      </Table>
 | 
					          </TableBody>
 | 
				
			||||||
 | 
					        </Table>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      <PaginationControl
 | 
				
			||||||
 | 
					        totalPage={Math.ceil(total / itemPerPage)}
 | 
				
			||||||
 | 
					        currentPage={page}
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										117
									
								
								web/src/components/ui/pagination.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								web/src/components/ui/pagination.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
				
			|||||||
 | 
					import type { ButtonProps } from "@/components/ui/button";
 | 
				
			||||||
 | 
					import { buttonVariants } from "@/components/ui/button";
 | 
				
			||||||
 | 
					import { cn } from "@/lib/utils";
 | 
				
			||||||
 | 
					import { ChevronLeft, ChevronRight, MoreHorizontal } from "lucide-react";
 | 
				
			||||||
 | 
					import Link from "next/link";
 | 
				
			||||||
 | 
					import * as React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
 | 
				
			||||||
 | 
					  <nav
 | 
				
			||||||
 | 
					    role="navigation"
 | 
				
			||||||
 | 
					    aria-label="pagination"
 | 
				
			||||||
 | 
					    className={cn("mx-auto flex w-full justify-center", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationContent = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLUListElement,
 | 
				
			||||||
 | 
					  React.ComponentProps<"ul">
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <ul
 | 
				
			||||||
 | 
					    ref={ref}
 | 
				
			||||||
 | 
					    className={cn("flex flex-row items-center gap-1", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					));
 | 
				
			||||||
 | 
					PaginationContent.displayName = "PaginationContent";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationItem = React.forwardRef<
 | 
				
			||||||
 | 
					  HTMLLIElement,
 | 
				
			||||||
 | 
					  React.ComponentProps<"li">
 | 
				
			||||||
 | 
					>(({ className, ...props }, ref) => (
 | 
				
			||||||
 | 
					  <li ref={ref} className={cn("", className)} {...props} />
 | 
				
			||||||
 | 
					));
 | 
				
			||||||
 | 
					PaginationItem.displayName = "PaginationItem";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PaginationLinkProps = {
 | 
				
			||||||
 | 
					  isActive?: boolean;
 | 
				
			||||||
 | 
					} & Pick<ButtonProps, "size"> &
 | 
				
			||||||
 | 
					  React.ComponentProps<typeof Link>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationLink = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  isActive,
 | 
				
			||||||
 | 
					  size = "icon",
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: PaginationLinkProps) => (
 | 
				
			||||||
 | 
					  <PaginationItem>
 | 
				
			||||||
 | 
					    <Link
 | 
				
			||||||
 | 
					      aria-current={isActive ? "page" : undefined}
 | 
				
			||||||
 | 
					      className={cn(
 | 
				
			||||||
 | 
					        buttonVariants({
 | 
				
			||||||
 | 
					          variant: isActive ? "outline" : "ghost",
 | 
				
			||||||
 | 
					          size,
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					        className
 | 
				
			||||||
 | 
					      )}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  </PaginationItem>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					PaginationLink.displayName = "PaginationLink";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationPrevious = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof PaginationLink>) => (
 | 
				
			||||||
 | 
					  <PaginationLink
 | 
				
			||||||
 | 
					    aria-label="Go to previous page"
 | 
				
			||||||
 | 
					    size="default"
 | 
				
			||||||
 | 
					    className={cn("gap-1 pl-2.5", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <ChevronLeft className="h-4 w-4" />
 | 
				
			||||||
 | 
					    <span>Previous</span>
 | 
				
			||||||
 | 
					  </PaginationLink>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					PaginationPrevious.displayName = "PaginationPrevious";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationNext = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<typeof PaginationLink>) => (
 | 
				
			||||||
 | 
					  <PaginationLink
 | 
				
			||||||
 | 
					    aria-label="Go to next page"
 | 
				
			||||||
 | 
					    size="default"
 | 
				
			||||||
 | 
					    className={cn("gap-1 pr-2.5", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <span>Next</span>
 | 
				
			||||||
 | 
					    <ChevronRight className="h-4 w-4" />
 | 
				
			||||||
 | 
					  </PaginationLink>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const PaginationEllipsis = ({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.ComponentProps<"span">) => (
 | 
				
			||||||
 | 
					  <span
 | 
				
			||||||
 | 
					    aria-hidden
 | 
				
			||||||
 | 
					    className={cn("flex h-9 w-9 items-center justify-center", className)}
 | 
				
			||||||
 | 
					    {...props}
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <MoreHorizontal className="h-4 w-4" />
 | 
				
			||||||
 | 
					    <span className="sr-only">More pages</span>
 | 
				
			||||||
 | 
					  </span>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export {
 | 
				
			||||||
 | 
					  Pagination,
 | 
				
			||||||
 | 
					  PaginationContent,
 | 
				
			||||||
 | 
					  PaginationEllipsis,
 | 
				
			||||||
 | 
					  PaginationItem,
 | 
				
			||||||
 | 
					  PaginationLink,
 | 
				
			||||||
 | 
					  PaginationNext,
 | 
				
			||||||
 | 
					  PaginationPrevious,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										15
									
								
								web/src/components/ui/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								web/src/components/ui/skeleton.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					import { cn } from "@/lib/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function Skeleton({
 | 
				
			||||||
 | 
					  className,
 | 
				
			||||||
 | 
					  ...props
 | 
				
			||||||
 | 
					}: React.HTMLAttributes<HTMLDivElement>) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      className={cn("animate-pulse rounded-md bg-muted", className)}
 | 
				
			||||||
 | 
					      {...props}
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { Skeleton }
 | 
				
			||||||
@ -1,14 +1,28 @@
 | 
				
			|||||||
 | 
					"use server";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { db } from "@/db/db";
 | 
					import { db } from "@/db/db";
 | 
				
			||||||
import { deploymentsTable, workflowRunsTable } from "@/db/schema";
 | 
					import { deploymentsTable, workflowRunsTable } from "@/db/schema";
 | 
				
			||||||
import { desc, eq, sql } from "drizzle-orm";
 | 
					import { count, desc, eq, sql } from "drizzle-orm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function findAllRuns(workflow_id: string) {
 | 
					type RunsSearchTypes = {
 | 
				
			||||||
 | 
					  workflow_id: string;
 | 
				
			||||||
 | 
					  limit: number;
 | 
				
			||||||
 | 
					  offset: number;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function findAllRuns({
 | 
				
			||||||
 | 
					  workflow_id,
 | 
				
			||||||
 | 
					  limit = 10,
 | 
				
			||||||
 | 
					  offset = 0,
 | 
				
			||||||
 | 
					}: RunsSearchTypes) {
 | 
				
			||||||
  return await db.query.workflowRunsTable.findMany({
 | 
					  return await db.query.workflowRunsTable.findMany({
 | 
				
			||||||
    where: eq(workflowRunsTable.workflow_id, workflow_id),
 | 
					    where: eq(workflowRunsTable.workflow_id, workflow_id),
 | 
				
			||||||
    orderBy: desc(workflowRunsTable.created_at),
 | 
					    orderBy: desc(workflowRunsTable.created_at),
 | 
				
			||||||
    limit: 10,
 | 
					    offset: offset,
 | 
				
			||||||
 | 
					    limit: limit,
 | 
				
			||||||
    extras: {
 | 
					    extras: {
 | 
				
			||||||
      number: sql<number>`row_number() over (order by created_at)`.as("number"),
 | 
					      number: sql<number>`row_number() over (order by created_at)`.as("number"),
 | 
				
			||||||
 | 
					      total: sql<number>`count(*) over ()`.as("total"),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    with: {
 | 
					    with: {
 | 
				
			||||||
      machine: {
 | 
					      machine: {
 | 
				
			||||||
@ -26,6 +40,20 @@ export async function findAllRuns(workflow_id: string) {
 | 
				
			|||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function findAllRunsWithCounts(props: RunsSearchTypes) {
 | 
				
			||||||
 | 
					  const a = await db
 | 
				
			||||||
 | 
					    .select({
 | 
				
			||||||
 | 
					      count: count(workflowRunsTable.id),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .from(workflowRunsTable)
 | 
				
			||||||
 | 
					    .where(eq(workflowRunsTable.workflow_id, props.workflow_id));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    allRuns: await findAllRuns(props),
 | 
				
			||||||
 | 
					    total: a[0].count,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function findAllDeployments(workflow_id: string) {
 | 
					export async function findAllDeployments(workflow_id: string) {
 | 
				
			||||||
  return await db.query.deploymentsTable.findMany({
 | 
					  return await db.query.deploymentsTable.findMany({
 | 
				
			||||||
    where: eq(deploymentsTable.workflow_id, workflow_id),
 | 
					    where: eq(deploymentsTable.workflow_id, workflow_id),
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user