From e3ee89104bdd3b642a6d831316d20efcb77529e1 Mon Sep 17 00:00:00 2001 From: djknaeckebrot Date: Tue, 17 Dec 2024 20:48:56 +0100 Subject: [PATCH] chore: remove tables and add new cards --- .../swarm/applications/data-table.tsx | 413 ++++++++---------- .../swarm/applications/show-applications.tsx | 191 ++++---- .../dashboard/swarm/containers/columns.tsx | 139 ++++++ .../dashboard/swarm/containers/data-table.tsx | 210 +++++++++ .../swarm/containers/show-container.tsx | 48 ++ .../dashboard/swarm/details/show-node.tsx | 94 ++-- .../dashboard/swarm/servers/columns.tsx | 168 +++++++ .../dashboard/swarm/servers/data-table.tsx | 210 +++++++++ .../dashboard/swarm/servers/show-server.tsx | 16 + .../dashboard/swarm/show/columns.tsx | 333 +++++++------- .../dashboard/swarm/show/deatils-card.tsx | 124 ++++++ .../components/layouts/navigation-tabs.tsx | 308 ++++++------- apps/dokploy/pages/dashboard/swarm.tsx | 19 +- 13 files changed, 1570 insertions(+), 703 deletions(-) create mode 100644 apps/dokploy/components/dashboard/swarm/containers/columns.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/containers/data-table.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/containers/show-container.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/servers/columns.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/servers/data-table.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/servers/show-server.tsx create mode 100644 apps/dokploy/components/dashboard/swarm/show/deatils-card.tsx diff --git a/apps/dokploy/components/dashboard/swarm/applications/data-table.tsx b/apps/dokploy/components/dashboard/swarm/applications/data-table.tsx index 1b192f7d..03915c19 100644 --- a/apps/dokploy/components/dashboard/swarm/applications/data-table.tsx +++ b/apps/dokploy/components/dashboard/swarm/applications/data-table.tsx @@ -1,156 +1,160 @@ "use client"; import { - type ColumnFiltersState, - type SortingState, - type VisibilityState, - type ColumnDef, - flexRender, - getCoreRowModel, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, } from "@tanstack/react-table"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; -import React from "react"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, -} from "@/components/ui/dropdown-menu"; -import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; import { Button } from "@/components/ui/button"; -import { ChevronDown } from "lucide-react"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, +} from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; +import { ChevronDown } from "lucide-react"; +import React from "react"; interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; + columns: ColumnDef[]; + data: TData[]; } export function DataTable({ - columns, - data, + columns, + data, }: DataTableProps) { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [] - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + const [pagination, setPagination] = React.useState({ + pageIndex: 0, //initial page index + pageSize: 8, //default page size + }); - const table = useReactTable({ - data, - columns, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - getCoreRowModel: getCoreRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFilteredRowModel: getFilteredRowModel(), - onColumnVisibilityChange: setColumnVisibility, - onRowSelectionChange: setRowSelection, - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, - }); + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); - return ( -
-
-
- - table.getColumn("Name")?.setFilterValue(event.target.value) - } - className="md:max-w-sm" - /> - - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {table?.getRowModel()?.rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - - No results. - {/* {isLoading ? ( + return ( +
+
+
+ + table.getColumn("Name")?.setFilterValue(event.target.value) + } + className="md:max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table?.getRowModel()?.rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + No results. + {/* {isLoading ? (
Loading... @@ -159,106 +163,35 @@ export function DataTable({ ) : ( <>No results. )} */} - - - )} - -
- {/*
- {isLoading ? ( -
- - Loading... - -
- ) : data?.length === 0 ? ( -
- - No results. - -
- ) : ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {table?.getRowModel()?.rows?.length ? ( - table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ))} - - )) - ) : ( - - - {isLoading ? ( -
- - Loading... - -
- ) : ( - <>No results. - )} -
-
- )} -
-
- )} -
*/} - {data && data?.length > 0 && ( -
-
- - -
-
- )} -
-
- ); + + + )} + + + + {data && data?.length > 0 && ( +
+
+ + +
+
+ )} + + + ); } diff --git a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx index 2ef632b9..4363adc1 100644 --- a/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx +++ b/apps/dokploy/components/dashboard/swarm/applications/show-applications.tsx @@ -1,122 +1,117 @@ -import React from "react"; +import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; -import { api } from "@/utils/api"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { DataTable } from "./data-table"; +import { api } from "@/utils/api"; +import { Layers, LoaderIcon } from "lucide-react"; +import React from "react"; import { columns } from "./columns"; -import { LoaderIcon } from "lucide-react"; +import { DataTable } from "./data-table"; interface Props { - nodeName: string; + nodeName: string; } interface ApplicationList { - ID: string; - Image: string; - Mode: string; - Name: string; - Ports: string; - Replicas: string; - CurrentState: string; - DesiredState: string; - Error: string; - Node: string; + ID: string; + Image: string; + Mode: string; + Name: string; + Ports: string; + Replicas: string; + CurrentState: string; + DesiredState: string; + Error: string; + Node: string; } const ShowNodeApplications = ({ nodeName }: Props) => { - const [loading, setLoading] = React.useState(true); - const { data: NodeApps, isLoading: NodeAppsLoading } = - api.swarm.getNodeApps.useQuery(); + const [loading, setLoading] = React.useState(true); + const { data: NodeApps, isLoading: NodeAppsLoading } = + api.swarm.getNodeApps.useQuery(); - let applicationList = ""; + let applicationList = ""; - if (NodeApps && NodeApps.length > 0) { - applicationList = NodeApps.map((app) => app.Name).join(" "); - } + if (NodeApps && NodeApps.length > 0) { + applicationList = NodeApps.map((app) => app.Name).join(" "); + } - const { data: NodeAppDetails, isLoading: NodeAppDetailsLoading } = - api.swarm.getAppInfos.useQuery({ appName: applicationList }); + const { data: NodeAppDetails, isLoading: NodeAppDetailsLoading } = + api.swarm.getAppInfos.useQuery({ appName: applicationList }); - if (NodeAppsLoading || NodeAppDetailsLoading) { - return ( - - - e.preventDefault()} - > - - - - - ); - } + if (NodeAppsLoading || NodeAppDetailsLoading) { + return ( + + + + + + ); + } - if (!NodeApps || !NodeAppDetails) { - return
No data found
; - } + if (!NodeApps || !NodeAppDetails) { + return
No data found
; + } - const combinedData: ApplicationList[] = NodeApps.flatMap((app) => { - const appDetails = - NodeAppDetails?.filter((detail) => - detail.Name.startsWith(`${app.Name}.`) - ) || []; + const combinedData: ApplicationList[] = NodeApps.flatMap((app) => { + const appDetails = + NodeAppDetails?.filter((detail) => + detail.Name.startsWith(`${app.Name}.`), + ) || []; - if (appDetails.length === 0) { - return [ - { - ...app, - CurrentState: "N/A", - DesiredState: "N/A", - Error: "", - Node: "N/A", - Ports: app.Ports, - }, - ]; - } + if (appDetails.length === 0) { + return [ + { + ...app, + CurrentState: "N/A", + DesiredState: "N/A", + Error: "", + Node: "N/A", + Ports: app.Ports, + }, + ]; + } - return appDetails.map((detail) => ({ - ...app, - CurrentState: detail.CurrentState, - DesiredState: detail.DesiredState, - Error: detail.Error, - Node: detail.Node, - Ports: detail.Ports || app.Ports, - })); - }); + return appDetails.map((detail) => ({ + ...app, + CurrentState: detail.CurrentState, + DesiredState: detail.DesiredState, + Error: detail.Error, + Node: detail.Node, + Ports: detail.Ports || app.Ports, + })); + }); - return ( - - - e.preventDefault()} - > - Show Applications - - - - - Node Applications - - See in detail the applications running on this node - - -
- -
- {/*
*/} -
-
- ); + return ( + + + + + + + Node Applications + + See in detail the applications running on this node + + +
+ +
+ {/*
*/} +
+
+ ); }; export default ShowNodeApplications; diff --git a/apps/dokploy/components/dashboard/swarm/containers/columns.tsx b/apps/dokploy/components/dashboard/swarm/containers/columns.tsx new file mode 100644 index 00000000..0ccf8e37 --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/containers/columns.tsx @@ -0,0 +1,139 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { ArrowUpDown, MoreHorizontal } from "lucide-react"; +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import type { Badge } from "@/components/ui/badge"; +import { ShowNodeConfig } from "../details/show-node"; +// import { ShowContainerConfig } from "../config/show-container-config"; +// import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; +// import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; +// import type { Container } from "./show-containers"; + +export interface ContainerList { + containerId: string; + name: string; + image: string; + ports: string; + state: string; + status: string; + serverId: string | null | undefined; +} + +export const columns: ColumnDef[] = [ + { + accessorKey: "ID", + accessorFn: (row) => row.containerId, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("containerId")}
; + }, + }, + { + accessorKey: "Name", + accessorFn: (row) => row.name, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("name")}
; + }, + }, + { + accessorKey: "Image", + accessorFn: (row) => row.image, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("image")}
; + }, + }, + { + accessorKey: "Ports", + accessorFn: (row) => row.ports, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("ports")}
; + }, + }, + { + accessorKey: "State", + accessorFn: (row) => row.state, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("state")}
; + }, + }, + { + accessorKey: "Status", + accessorFn: (row) => row.status, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("status")}
; + }, + }, +]; diff --git a/apps/dokploy/components/dashboard/swarm/containers/data-table.tsx b/apps/dokploy/components/dashboard/swarm/containers/data-table.tsx new file mode 100644 index 00000000..95b4498e --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/containers/data-table.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; +import { ChevronDown } from "lucide-react"; +import React from "react"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + isLoading: boolean; +} + +export function DataTable({ + columns, + data, + isLoading, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + return ( +
+
+
+ + table.getColumn("Name")?.setFilterValue(event.target.value) + } + className="md:max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+ +
+ {isLoading ? ( +
+ + Loading... + +
+ ) : data?.length === 0 ? ( +
+ + No results. + +
+ ) : ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table?.getRowModel()?.rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + {isLoading ? ( +
+ + Loading... + +
+ ) : ( + <>No results. + )} +
+
+ )} +
+
+ )} +
+ {data && data?.length > 0 && ( +
+
+ + +
+
+ )} +
+
+ ); +} diff --git a/apps/dokploy/components/dashboard/swarm/containers/show-container.tsx b/apps/dokploy/components/dashboard/swarm/containers/show-container.tsx new file mode 100644 index 00000000..dec6c6e7 --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/containers/show-container.tsx @@ -0,0 +1,48 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; +import { api } from "@/utils/api"; +import React from "react"; +import { ShowContainers } from "../../docker/show/show-containers"; +import { columns } from "./columns"; +import { DataTable } from "./data-table"; +// import { columns } from "./columns"; +// import { DataTable } from "./data-table"; + +interface Props { + serverId: string; +} + +const ShowNodeContainers = ({ serverId }: Props) => { + return ( + + + e.preventDefault()} + > + Show Container + + + + + Node Container + + See all containers running on this node + + +
+ +
+
+
+ ); +}; + +export default ShowNodeContainers; diff --git a/apps/dokploy/components/dashboard/swarm/details/show-node.tsx b/apps/dokploy/components/dashboard/swarm/details/show-node.tsx index 9a092152..4f751805 100644 --- a/apps/dokploy/components/dashboard/swarm/details/show-node.tsx +++ b/apps/dokploy/components/dashboard/swarm/details/show-node.tsx @@ -1,54 +1,60 @@ -import React from "react"; +import { CodeEditor } from "@/components/shared/code-editor"; +import { Button } from "@/components/ui/button"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, - DialogTrigger, + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, } from "@/components/ui/dialog"; import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; -import { CodeEditor } from "@/components/shared/code-editor"; import { api } from "@/utils/api"; +import { Settings } from "lucide-react"; +import React from "react"; interface Props { - nodeId: string; + nodeId: string; } export const ShowNodeConfig = ({ nodeId }: Props) => { - const { data, isLoading } = api.swarm.getNodeInfo.useQuery({ nodeId }); - return ( - - - e.preventDefault()} - > - View Config - - - - - Node Config - - See in detail the metadata of this node - - -
- -
-              {/* {JSON.stringify(data, null, 2)} */}
-              
-            
-
-
-
-
- ); + const { data, isLoading } = api.swarm.getNodeInfo.useQuery({ nodeId }); + return ( + + + {/* e.preventDefault()} + > + Show Config + */} + + + + + Node Config + + See in detail the metadata of this node + + +
+ +
+							{/* {JSON.stringify(data, null, 2)} */}
+							
+						
+
+
+
+
+ ); }; diff --git a/apps/dokploy/components/dashboard/swarm/servers/columns.tsx b/apps/dokploy/components/dashboard/swarm/servers/columns.tsx new file mode 100644 index 00000000..02b013db --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/servers/columns.tsx @@ -0,0 +1,168 @@ +import type { ColumnDef } from "@tanstack/react-table"; +import { ArrowUpDown, MoreHorizontal } from "lucide-react"; +import * as React from "react"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; + +import type { Badge } from "@/components/ui/badge"; +import { ShowContainers } from "../../docker/show/show-containers"; +import ShowNodeContainers from "../containers/show-container"; +import { ShowNodeConfig } from "../details/show-node"; +// import { ShowContainerConfig } from "../config/show-container-config"; +// import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; +// import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; +// import type { Container } from "./show-containers"; + +export interface ServerList { + totalSum: number; + serverId: string; + name: string; + description: string | null; + ipAddress: string; + port: number; + username: string; + appName: string; + enableDockerCleanup: boolean; + createdAt: string; + adminId: string; + serverStatus: "active" | "inactive"; + command: string; + sshKeyId: string | null; +} + +export const columns: ColumnDef[] = [ + { + accessorKey: "serverId", + accessorFn: (row) => row.serverId, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("serverId")}
; + }, + }, + { + accessorKey: "name", + accessorFn: (row) => row.name, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("name")}
; + }, + }, + { + accessorKey: "ipAddress", + accessorFn: (row) => row.ipAddress, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("ipAddress")}
; + }, + }, + { + accessorKey: "port", + accessorFn: (row) => row.port, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("port")}
; + }, + }, + { + accessorKey: "username", + accessorFn: (row) => row.username, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("username")}
; + }, + }, + { + accessorKey: "createdAt", + accessorFn: (row) => row.createdAt, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("createdAt")}
; + }, + }, + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + return ( + + + + + + Actions + + + + ); + }, + }, +]; diff --git a/apps/dokploy/components/dashboard/swarm/servers/data-table.tsx b/apps/dokploy/components/dashboard/swarm/servers/data-table.tsx new file mode 100644 index 00000000..95b4498e --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/servers/data-table.tsx @@ -0,0 +1,210 @@ +"use client"; + +import { + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; +import { ChevronDown } from "lucide-react"; +import React from "react"; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + isLoading: boolean; +} + +export function DataTable({ + columns, + data, + isLoading, +}: DataTableProps) { + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState( + [], + ); + const [columnVisibility, setColumnVisibility] = + React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + + const table = useReactTable({ + data, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + }, + }); + + return ( +
+
+
+ + table.getColumn("Name")?.setFilterValue(event.target.value) + } + className="md:max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + +
+ +
+ {isLoading ? ( +
+ + Loading... + +
+ ) : data?.length === 0 ? ( +
+ + No results. + +
+ ) : ( + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table?.getRowModel()?.rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext(), + )} + + ))} + + )) + ) : ( + + + {isLoading ? ( +
+ + Loading... + +
+ ) : ( + <>No results. + )} +
+
+ )} +
+
+ )} +
+ {data && data?.length > 0 && ( +
+
+ + +
+
+ )} +
+
+ ); +} diff --git a/apps/dokploy/components/dashboard/swarm/servers/show-server.tsx b/apps/dokploy/components/dashboard/swarm/servers/show-server.tsx new file mode 100644 index 00000000..0486b164 --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/servers/show-server.tsx @@ -0,0 +1,16 @@ +import { api } from "@/utils/api"; +import React from "react"; +import { columns } from "./columns"; +import { DataTable } from "./data-table"; + +function ShowApplicationServers() { + const { data, isLoading } = api.server.all.useQuery(); + + console.log(data); + + return ( + + ); +} + +export default ShowApplicationServers; diff --git a/apps/dokploy/components/dashboard/swarm/show/columns.tsx b/apps/dokploy/components/dashboard/swarm/show/columns.tsx index ba11d749..b0774936 100644 --- a/apps/dokploy/components/dashboard/swarm/show/columns.tsx +++ b/apps/dokploy/components/dashboard/swarm/show/columns.tsx @@ -4,180 +4,181 @@ import * as React from "react"; import { Button } from "@/components/ui/button"; import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuTrigger, + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Badge } from "@/components/ui/badge"; -import { ShowNodeConfig } from "../details/show-node"; import ShowNodeApplications from "../applications/show-applications"; +import ShowContainers from "../containers/show-container"; +import { ShowNodeConfig } from "../details/show-node"; // import { ShowContainerConfig } from "../config/show-container-config"; // import { ShowDockerModalLogs } from "../logs/show-docker-modal-logs"; // import { DockerTerminalModal } from "../terminal/docker-terminal-modal"; // import type { Container } from "./show-containers"; export interface SwarmList { - ID: string; - Hostname: string; - Availability: string; - EngineVersion: string; - Status: string; - ManagerStatus: string; - TLSStatus: string; + ID: string; + Hostname: string; + Availability: string; + EngineVersion: string; + Status: string; + ManagerStatus: string; + TLSStatus: string; } export const columns: ColumnDef[] = [ - { - accessorKey: "ID", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - return
{row.getValue("ID")}
; - }, - }, - { - accessorKey: "EngineVersion", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - return
{row.getValue("EngineVersion")}
; - }, - }, - { - accessorKey: "Hostname", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - return
{row.getValue("Hostname")}
; - }, - }, - // { - // accessorKey: "Status", - // header: ({ column }) => { - // return ( - // - // ); - // }, - // cell: ({ row }) => { - // const value = row.getValue("status") as string; - // return ( - //
- // - // {value} - // - //
- // ); - // }, - // }, - { - accessorKey: "Availability", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => { - const value = row.getValue("Availability") as string; - return ( -
- - {value} - -
- ); - }, - }, - { - accessorKey: "ManagerStatus", - header: ({ column }) => { - return ( - - ); - }, - cell: ({ row }) => ( -
{row.getValue("ManagerStatus")}
- ), - }, - { - id: "actions", - enableHiding: false, - cell: ({ row }) => { - return ( - - - - - - Actions - - - {/* { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("ID")}
; + }, + }, + { + accessorKey: "EngineVersion", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("EngineVersion")}
; + }, + }, + { + accessorKey: "Hostname", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return
{row.getValue("Hostname")}
; + }, + }, + // { + // accessorKey: "Status", + // header: ({ column }) => { + // return ( + // + // ); + // }, + // cell: ({ row }) => { + // const value = row.getValue("status") as string; + // return ( + //
+ // + // {value} + // + //
+ // ); + // }, + // }, + { + accessorKey: "Availability", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + const value = row.getValue("Availability") as string; + return ( +
+ + {value} + +
+ ); + }, + }, + { + accessorKey: "ManagerStatus", + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => ( +
{row.getValue("ManagerStatus")}
+ ), + }, + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + return ( + + + + + + Actions + + + {/* @@ -193,9 +194,9 @@ export const columns: ColumnDef[] = [ > Terminal */} - - - ); - }, - }, +
+
+ ); + }, + }, ]; diff --git a/apps/dokploy/components/dashboard/swarm/show/deatils-card.tsx b/apps/dokploy/components/dashboard/swarm/show/deatils-card.tsx new file mode 100644 index 00000000..1d12ab52 --- /dev/null +++ b/apps/dokploy/components/dashboard/swarm/show/deatils-card.tsx @@ -0,0 +1,124 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + AlertCircle, + CheckCircle, + HelpCircle, + Layers, + Settings, +} from "lucide-react"; +import { useState } from "react"; +import ShowNodeApplications from "../applications/show-applications"; +import { ShowNodeConfig } from "../details/show-node"; + +export interface SwarmList { + ID: string; + Hostname: string; + Availability: string; + EngineVersion: string; + Status: string; + ManagerStatus: string; + TLSStatus: string; +} + +interface NodeCardProps { + node: SwarmList; +} + +export function NodeCard({ node }: NodeCardProps) { + const [showConfig, setShowConfig] = useState(false); + const [showServices, setShowServices] = useState(false); + + const getStatusIcon = (status: string) => { + switch (status) { + case "Ready": + return ; + case "Down": + return ; + default: + return ; + } + }; + + return ( + + + + + {getStatusIcon(node.Status)} + {node.Hostname} + + + {node.ManagerStatus || "Worker"} + + + + +
+
+ Status: + {node.Status} +
+
+ Availability: + {node.Availability} +
+
+ Engine Version: + {node.EngineVersion} +
+
+ TLS Status: + {node.TLSStatus} +
+
+
+ + {/* + + + + + + Node Configuration + +
+
+									{JSON.stringify(node, null, 2)}
+								
+
+
+
*/} + + {/* + + + + + + Node Services + +
+

Service information would be displayed here.

+
+
+
*/} +
+
+
+ ); +} diff --git a/apps/dokploy/components/layouts/navigation-tabs.tsx b/apps/dokploy/components/layouts/navigation-tabs.tsx index c9392b10..46e590a7 100644 --- a/apps/dokploy/components/layouts/navigation-tabs.tsx +++ b/apps/dokploy/components/layouts/navigation-tabs.tsx @@ -7,176 +7,176 @@ import { useEffect, useMemo, useState } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs"; interface TabInfo { - label: string; - tabLabel?: string; - description: string; - index: string; - type: TabState; - isShow?: ({ rol, user }: { rol?: Auth["rol"]; user?: User }) => boolean; + label: string; + tabLabel?: string; + description: string; + index: string; + type: TabState; + isShow?: ({ rol, user }: { rol?: Auth["rol"]; user?: User }) => boolean; } export type TabState = - | "projects" - | "monitoring" - | "settings" - | "traefik" - | "requests" - | "docker" - | "swarm"; + | "projects" + | "monitoring" + | "settings" + | "traefik" + | "requests" + | "docker" + | "swarm"; const getTabMaps = (isCloud: boolean) => { - const elements: TabInfo[] = [ - { - label: "Projects", - description: "Manage your projects", - index: "/dashboard/projects", - type: "projects", - }, - ]; + const elements: TabInfo[] = [ + { + label: "Projects", + description: "Manage your projects", + index: "/dashboard/projects", + type: "projects", + }, + ]; - if (!isCloud) { - elements.push( - { - label: "Monitoring", - description: "Monitor your projects", - index: "/dashboard/monitoring", - type: "monitoring", - }, - { - label: "Traefik", - tabLabel: "Traefik File System", - description: "Manage your traefik", - index: "/dashboard/traefik", - isShow: ({ rol, user }) => { - return Boolean(rol === "admin" || user?.canAccessToTraefikFiles); - }, - type: "traefik", - }, - { - label: "Docker", - description: "Manage your docker", - index: "/dashboard/docker", - isShow: ({ rol, user }) => { - return Boolean(rol === "admin" || user?.canAccessToDocker); - }, - type: "docker", - }, - { - label: "Swarm", - description: "Manage your docker swarm", - index: "/dashboard/swarm", - isShow: ({ rol, user }) => { - return Boolean(rol === "admin" || user?.canAccessToDocker); - }, - type: "swarm", - }, - { - label: "Requests", - description: "Manage your requests", - index: "/dashboard/requests", - isShow: ({ rol, user }) => { - return Boolean(rol === "admin" || user?.canAccessToDocker); - }, - type: "requests", - } - ); - } + if (!isCloud) { + elements.push( + { + label: "Monitoring", + description: "Monitor your projects", + index: "/dashboard/monitoring", + type: "monitoring", + }, + { + label: "Traefik", + tabLabel: "Traefik File System", + description: "Manage your traefik", + index: "/dashboard/traefik", + isShow: ({ rol, user }) => { + return Boolean(rol === "admin" || user?.canAccessToTraefikFiles); + }, + type: "traefik", + }, + { + label: "Docker", + description: "Manage your docker", + index: "/dashboard/docker", + isShow: ({ rol, user }) => { + return Boolean(rol === "admin" || user?.canAccessToDocker); + }, + type: "docker", + }, + { + label: "Swarm & Server", + description: "Manage your docker swarm and Servers", + index: "/dashboard/swarm", + isShow: ({ rol, user }) => { + return Boolean(rol === "admin" || user?.canAccessToDocker); + }, + type: "swarm", + }, + { + label: "Requests", + description: "Manage your requests", + index: "/dashboard/requests", + isShow: ({ rol, user }) => { + return Boolean(rol === "admin" || user?.canAccessToDocker); + }, + type: "requests", + }, + ); + } - elements.push({ - label: "Settings", - description: "Manage your settings", - type: "settings", - index: isCloud - ? "/dashboard/settings/profile" - : "/dashboard/settings/server", - }); + elements.push({ + label: "Settings", + description: "Manage your settings", + type: "settings", + index: isCloud + ? "/dashboard/settings/profile" + : "/dashboard/settings/server", + }); - return elements; + return elements; }; interface Props { - tab: TabState; - children: React.ReactNode; + tab: TabState; + children: React.ReactNode; } export const NavigationTabs = ({ tab, children }: Props) => { - const router = useRouter(); - const { data } = api.auth.get.useQuery(); - const [activeTab, setActiveTab] = useState(tab); - const { data: isCloud } = api.settings.isCloud.useQuery(); - const tabMap = useMemo(() => getTabMaps(isCloud ?? false), [isCloud]); - const { data: user } = api.user.byAuthId.useQuery( - { - authId: data?.id || "", - }, - { - enabled: !!data?.id && data?.rol === "user", - } - ); + const router = useRouter(); + const { data } = api.auth.get.useQuery(); + const [activeTab, setActiveTab] = useState(tab); + const { data: isCloud } = api.settings.isCloud.useQuery(); + const tabMap = useMemo(() => getTabMaps(isCloud ?? false), [isCloud]); + const { data: user } = api.user.byAuthId.useQuery( + { + authId: data?.id || "", + }, + { + enabled: !!data?.id && data?.rol === "user", + }, + ); - useEffect(() => { - setActiveTab(tab); - }, [tab]); + useEffect(() => { + setActiveTab(tab); + }, [tab]); - const activeTabInfo = useMemo(() => { - return tabMap.find((tab) => tab.type === activeTab); - }, [activeTab]); + const activeTabInfo = useMemo(() => { + return tabMap.find((tab) => tab.type === activeTab); + }, [activeTab]); - return ( -
-
-
-

- {activeTabInfo?.label} -

-

- {activeTabInfo?.description} -

-
- {tab === "projects" && - (data?.rol === "admin" || user?.canCreateProjects) && } -
-
- { - setActiveTab(e as TabState); - const tab = tabMap.find((tab) => tab.type === e); - router.push(tab?.index || ""); - }} - > -
- - {tabMap.map((tab, index) => { - if (tab?.isShow && !tab?.isShow?.({ rol: data?.rol, user })) { - return null; - } - return ( - - - {tab?.tabLabel || tab?.label} - - {tab.type === activeTab && ( -
-
-
- )} - - ); - })} - -
+ return ( +
+
+
+

+ {activeTabInfo?.label} +

+

+ {activeTabInfo?.description} +

+
+ {tab === "projects" && + (data?.rol === "admin" || user?.canCreateProjects) && } +
+
+ { + setActiveTab(e as TabState); + const tab = tabMap.find((tab) => tab.type === e); + router.push(tab?.index || ""); + }} + > +
+ + {tabMap.map((tab, index) => { + if (tab?.isShow && !tab?.isShow?.({ rol: data?.rol, user })) { + return null; + } + return ( + + + {tab?.tabLabel || tab?.label} + + {tab.type === activeTab && ( +
+
+
+ )} + + ); + })} + +
- - {children} - - -
-
- ); + + {children} + + +
+
+ ); }; diff --git a/apps/dokploy/pages/dashboard/swarm.tsx b/apps/dokploy/pages/dashboard/swarm.tsx index b608efc0..11035da7 100644 --- a/apps/dokploy/pages/dashboard/swarm.tsx +++ b/apps/dokploy/pages/dashboard/swarm.tsx @@ -1,6 +1,11 @@ +import { ShowServers } from "@/components/dashboard/settings/servers/show-servers"; +import SwarmMonitorCard from "@/components/dashboard/swarm/monitoring-card"; +import ShowApplicationServers from "@/components/dashboard/swarm/servers/show-server"; import ShowSwarmNodes from "@/components/dashboard/swarm/show/show-nodes"; import { DashboardLayout } from "@/components/layouts/dashboard-layout"; +import { Separator } from "@/components/ui/separator"; import { appRouter } from "@/server/api/root"; +import { api } from "@/utils/api"; import { IS_CLOUD, validateRequest } from "@dokploy/server"; import { createServerSideHelpers } from "@trpc/react-query/server"; import type { GetServerSidePropsContext } from "next"; @@ -8,7 +13,19 @@ import React, { type ReactElement } from "react"; import superjson from "superjson"; const Dashboard = () => { - return ; + return ( + <> +
+ +
+ + {/*

Swarm Nodes

+ + +

Server Nodes

+ */} + + ); }; export default Dashboard;