diff --git a/package-lock.json b/package-lock.json index 657ed59..0ebb9d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "jotai-zustand": "^0.6.0", @@ -24,6 +26,7 @@ "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "sonner": "^1.6.1", + "swr": "^2.2.5", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8", @@ -1147,6 +1150,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", + "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -2035,6 +2068,39 @@ "win32" ] }, + "node_modules/@tanstack/react-table": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.20.5.tgz", + "integrity": "sha512-WEHopKw3znbUZ61s9i0+i9g8drmDo6asTWbrQh8Us63DAk/M0FkmIqERew6P71HI75ksZ2Pxyuf4vvKh9rAkiA==", + "license": "MIT", + "dependencies": { + "@tanstack/table-core": "8.20.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.20.5", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz", + "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2731,6 +2797,12 @@ "node": ">=6" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -4726,6 +4798,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "license": "MIT", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwind-merge": { "version": "2.5.4", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", @@ -4989,6 +5074,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 5af2d6f..9867592 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "dependencies": { "@hookform/resolvers": "^3.9.1", "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-navigation-menu": "^1.2.1", "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "jotai-zustand": "^0.6.0", @@ -26,6 +28,7 @@ "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "sonner": "^1.6.1", + "swr": "^2.2.5", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8", diff --git a/src/api/api.ts b/src/api/api.ts index 843280c..1788028 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -46,3 +46,7 @@ export async function fetcher(method: FetcherMethod, path: string, data?: any } return responseData.data; } + +export async function swrFetcher(input: string | URL | globalThis.Request, init?: RequestInit) { + return fetcher(init?.method as FetcherMethod, input.toString(), init?.body); +} \ No newline at end of file diff --git a/src/components/header.tsx b/src/components/header.tsx index a900aa4..eddf3ed 100644 --- a/src/components/header.tsx +++ b/src/components/header.tsx @@ -22,7 +22,7 @@ export default function Header() { - + 哪吒监控 @@ -30,7 +30,7 @@ export default function Header() { { profile && <> - + Server diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..ddbdd01 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { Check } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)) +Checkbox.displayName = CheckboxPrimitive.Root.displayName + +export { Checkbox } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..7f3502f --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)) +Table.displayName = "Table" + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableHeader.displayName = "TableHeader" + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableBody.displayName = "TableBody" + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className + )} + {...props} + /> +)) +TableFooter.displayName = "TableFooter" + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableRow.displayName = "TableRow" + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableHead.displayName = "TableHead" + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) +TableCell.displayName = "TableCell" + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +TableCaption.displayName = "TableCaption" + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +} diff --git a/src/main.tsx b/src/main.tsx index a0f1313..e4f287b 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -11,6 +11,7 @@ import Root from "./routes/root"; import ErrorPage from "./error-page"; import ProtectedRoute from './routes/protect'; import LoginPage from './routes/login'; +import ServerPage from './routes/server'; import { AuthProvider } from './hooks/useAuth'; const router = createBrowserRouter([ @@ -23,6 +24,10 @@ const router = createBrowserRouter([ path: "/dashboard/login", element: , }, + { + path: "/dashboard", + element: , + }, ] }, ]); diff --git a/src/routes/server.tsx b/src/routes/server.tsx new file mode 100644 index 0000000..9b98e4b --- /dev/null +++ b/src/routes/server.tsx @@ -0,0 +1,116 @@ +import { swrFetcher } from "@/api/api" +import { Checkbox } from "@/components/ui/checkbox" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { Server } from "@/types" +import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" +import useSWR from "swr" + +export default function ServerPage() { + const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + header: "ID", + accessorKey: "id", + accessorFn: (row) => row.id, + }, + { + header: "Name", + accessorKey: "name", + accessorFn: (row) => row.name, + }, + { + header: "Host", + accessorKey: "host.ip", + accessorFn: (row) => row.host.ip, + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + const s = row.original + return ( + <>{s.id} + ) + }, + }, + ] + + const { data, error, isLoading } = useSWR('/api/v1/server', swrFetcher) + + const table = useReactTable({ + data: data ?? [], + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return
+
+

+ Server +

+
+ + + {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. + + + )} + +
+
+} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 6c73ff2..e967cc0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,3 +1,4 @@ +export * from './server'; export * from './user'; export * from './mainStore'; export * from './authContext'; \ No newline at end of file diff --git a/src/types/server.tsx b/src/types/server.tsx new file mode 100644 index 0000000..8fd78e4 --- /dev/null +++ b/src/types/server.tsx @@ -0,0 +1,57 @@ +export interface ServerHost { + arch: string; + bootTime: number; + countryCode: string; + cpu: string[]; + diskTotal: number; + gpu: string[]; + memTotal: number; + platform: string; + platformVersion: string; + swapTotal: number; + version: string; + virtualization: string; + ip: string; +} + +export interface ServerTemperature { + name: string; + temperature: number; +} + +export interface ServerState { + cpu: number; + diskUsed: number; + gpu: number[]; + load1: number; + load15: number; + load5: number; + memUsed: number; + netInSpeed: number; + netInTransfer: number; + netOutSpeed: number; + netOutTransfer: number; + processCount: number; + swapUsed: number; + tcpConnCount: number; + temperatures: ServerTemperature[]; + udpConnCount: number; + uptime: number; + updated_at: string; + uuid: string; +} + +export interface Server { + id: number; + name: string; + ddns_profiles: number[]; + created_at: string; + display_index: number; + enable_ddns: boolean; + hide_for_guest: boolean; + host: ServerHost; + last_active: string; + note: string; + public_note: string; + state: ServerState; +} \ No newline at end of file