diff --git a/web/bun.lockb b/web/bun.lockb index 5e71c92..da357e6 100755 Binary files a/web/bun.lockb and b/web/bun.lockb differ diff --git a/web/package.json b/web/package.json index 0fd9a24..c075a23 100644 --- a/web/package.json +++ b/web/package.json @@ -66,6 +66,7 @@ "next-plausible": "^3.12.0", "next-themes": "^0.2.1", "next-usequerystate": "^1.13.2", + "pg": "^8.11.3", "react": "^18", "react-day-picker": "^8.9.1", "react-dom": "^18", diff --git a/web/src/app/(app)/api-keys/loading.tsx b/web/src/app/(app)/api-keys/loading.tsx new file mode 100644 index 0000000..9ff4783 --- /dev/null +++ b/web/src/app/(app)/api-keys/loading.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { LoadingPageWrapper } from "@/components/LoadingWrapper"; +import { usePathname } from "next/navigation"; + +export default function Loading() { + const pathName = usePathname(); + return ; +} diff --git a/web/src/app/(app)/machines/loading.tsx b/web/src/app/(app)/machines/loading.tsx new file mode 100644 index 0000000..9ff4783 --- /dev/null +++ b/web/src/app/(app)/machines/loading.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { LoadingPageWrapper } from "@/components/LoadingWrapper"; +import { usePathname } from "next/navigation"; + +export default function Loading() { + const pathName = usePathname(); + return ; +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/@deployment/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/@deployment/page.tsx new file mode 100644 index 0000000..34eb1f0 --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/@deployment/page.tsx @@ -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 ( + + + Deployments + + + + + + + + + ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/@runs/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/@runs/page.tsx new file mode 100644 index 0000000..2843009 --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/@runs/page.tsx @@ -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 ( + + + Run + + + + + + + + + ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/@workflow/loading.tsx b/web/src/app/(app)/workflows/[workflow_id]/@workflow/loading.tsx new file mode 100644 index 0000000..175e6f8 --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/@workflow/loading.tsx @@ -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 ( + + + + ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx new file mode 100644 index 0000000..5ed3b8b --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/@workflow/page.tsx @@ -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 ( + + + {workflow?.name} + + {getRelativeTime(workflow?.updated_at)} + + + + +
+ + + + + + +
+ + + +
+
+ ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/layout.tsx b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx new file mode 100644 index 0000000..7a2f06d --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/layout.tsx @@ -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 ( +
+
+ {workflow} + {deployment} +
+ + {runs} +
+ ); +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/loading.tsx b/web/src/app/(app)/workflows/[workflow_id]/loading.tsx new file mode 100644 index 0000000..372234d --- /dev/null +++ b/web/src/app/(app)/workflows/[workflow_id]/loading.tsx @@ -0,0 +1,6 @@ +import { LoadingPageWrapper } from "@/components/LoadingWrapper"; + +export default function Loading() { + // You can add any UI inside Loading, including a Skeleton. + return ; +} diff --git a/web/src/app/(app)/workflows/[workflow_id]/page.tsx b/web/src/app/(app)/workflows/[workflow_id]/page.tsx deleted file mode 100644 index e56113c..0000000 --- a/web/src/app/(app)/workflows/[workflow_id]/page.tsx +++ /dev/null @@ -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 ( -
-
- - - {workflow?.name} - - {getRelativeTime(workflow?.updated_at)} - - - - -
- - - - - - -
- - - -
-
- - - Deployments - - - - - - -
- - - - Run - - - - - - -
- ); -} diff --git a/web/src/app/(app)/workflows/loading.tsx b/web/src/app/(app)/workflows/loading.tsx new file mode 100644 index 0000000..9ff4783 --- /dev/null +++ b/web/src/app/(app)/workflows/loading.tsx @@ -0,0 +1,9 @@ +"use client"; + +import { LoadingPageWrapper } from "@/components/LoadingWrapper"; +import { usePathname } from "next/navigation"; + +export default function Loading() { + const pathName = usePathname(); + return ; +} diff --git a/web/src/components/LoadingWrapper.tsx b/web/src/components/LoadingWrapper.tsx new file mode 100644 index 0000000..84b8fd5 --- /dev/null +++ b/web/src/components/LoadingWrapper.tsx @@ -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 ( + + Fetching {props.tag} + + } + > + {props.children} + + ); +} + +export function LoadingPageWrapper(props: { + tag: string; + children?: React.ReactNode; + className?: string; +}) { + return ( +
+ Fetching {props.tag} +
+ ); +} diff --git a/web/src/components/PaginationControl.tsx b/web/src/components/PaginationControl.tsx new file mode 100644 index 0000000..19825dd --- /dev/null +++ b/web/src/components/PaginationControl.tsx @@ -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 ( + + + 1 + ? `?page=${props.currentPage - 1}` + : `?page=${props.currentPage}` + } + /> + + {props.currentPage} + + + + + + + + ); +} diff --git a/web/src/components/RunsTable.tsx b/web/src/components/RunsTable.tsx index 75d61a3..8ac592f 100644 --- a/web/src/components/RunsTable.tsx +++ b/web/src/components/RunsTable.tsx @@ -1,5 +1,9 @@ -import { findAllDeployments, findAllRuns } from "../server/findAllRuns"; +import { + findAllDeployments, + findAllRunsWithCounts, +} from "../server/findAllRuns"; import { DeploymentDisplay } from "./DeploymentDisplay"; +import { PaginationControl } from "./PaginationControl"; import { RunDisplay } from "./RunDisplay"; import { Table, @@ -9,29 +13,51 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { parseAsInteger } from "next-usequerystate"; -export async function RunsTable(props: { workflow_id: string }) { - const allRuns = await findAllRuns(props.workflow_id); +const itemPerPage = 4; +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 ( -
- - A list of your recent runs. - - - Number - Machine - Time - Version - Live Status - Status - - - - {allRuns.map((run) => ( - - ))} - -
+
+
+ + {/* A list of your recent runs. */} + + + Number + Machine + Time + Version + Live Status + Status + + + + {allRuns.map((run) => ( + + ))} + +
+
+ +
); } diff --git a/web/src/components/ui/pagination.tsx b/web/src/components/ui/pagination.tsx new file mode 100644 index 0000000..5e5b375 --- /dev/null +++ b/web/src/components/ui/pagination.tsx @@ -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">) => ( +