From 22da283031aec33a4b372302f5f5ce8cc68b0ffd Mon Sep 17 00:00:00 2001 From: uubulb Date: Fri, 15 Nov 2024 16:49:10 +0800 Subject: [PATCH 1/5] init service page --- src/hooks/useMainStore.ts | 2 +- src/main.tsx | 5 ++ src/routes/server.tsx | 4 +- src/routes/service.tsx | 121 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 src/routes/service.tsx diff --git a/src/hooks/useMainStore.ts b/src/hooks/useMainStore.ts index 8122967..3e340e4 100644 --- a/src/hooks/useMainStore.ts +++ b/src/hooks/useMainStore.ts @@ -1,4 +1,4 @@ -import { MainStore, User } from '@/types' +import { MainStore, ModelUser as User } from '@/types' import { create } from 'zustand' import { persist, createJSONStorage } from 'zustand/middleware' diff --git a/src/main.tsx b/src/main.tsx index e4f287b..9aaaaca 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -12,6 +12,7 @@ import ErrorPage from "./error-page"; import ProtectedRoute from './routes/protect'; import LoginPage from './routes/login'; import ServerPage from './routes/server'; +import ServicePage from './routes/service'; import { AuthProvider } from './hooks/useAuth'; const router = createBrowserRouter([ @@ -28,6 +29,10 @@ const router = createBrowserRouter([ path: "/dashboard", element: , }, + { + path: "/dashboard/service", + element: , + }, ] }, ]); diff --git a/src/routes/server.tsx b/src/routes/server.tsx index 9b98e4b..f08c3a5 100644 --- a/src/routes/server.tsx +++ b/src/routes/server.tsx @@ -1,7 +1,7 @@ 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 { ModelServer as Server } from "@/types" import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" import useSWR from "swr" @@ -42,7 +42,7 @@ export default function ServerPage() { { header: "Host", accessorKey: "host.ip", - accessorFn: (row) => row.host.ip, + accessorFn: (row) => row.host?.ip, }, { id: "actions", diff --git a/src/routes/service.tsx b/src/routes/service.tsx new file mode 100644 index 0000000..c78c8fc --- /dev/null +++ b/src/routes/service.tsx @@ -0,0 +1,121 @@ +import { swrFetcher } from "@/api/api" +import { Checkbox } from "@/components/ui/checkbox" +import { Button } from "@/components/ui/button" +import { Plus } from "lucide-react" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { ModelService as Service } from "@/types" +import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" +import useSWR from "swr" + +export default function ServicePage() { + 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: "Type", + accessorKey: "service.type", + accessorFn: (row) => row.type, + }, + { + id: "actions", + header: "Actions", + cell: ({ row }) => { + const s = row.original + return ( + <>{s.id} + ) + }, + }, + ] + + const { data, error, isLoading } = useSWR('/api/v1/service', swrFetcher) + + const table = useReactTable({ + data: data ?? [], + columns, + getCoreRowModel: getCoreRowModel(), + }) + + return
+
+

+ Service +

+ +
+ + + {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 From 4237b0b286758060108c85a73211b11705b2e912 Mon Sep 17 00:00:00 2001 From: uubulb Date: Fri, 15 Nov 2024 17:36:15 +0800 Subject: [PATCH 2/5] add padding --- src/routes/server.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/server.tsx b/src/routes/server.tsx index f08c3a5..760ce95 100644 --- a/src/routes/server.tsx +++ b/src/routes/server.tsx @@ -65,7 +65,7 @@ export default function ServerPage() { }) return
-
+

Server

From 599ff6c72ae17549b43208ccc28ced8765b8702e Mon Sep 17 00:00:00 2001 From: uubulb Date: Fri, 15 Nov 2024 18:11:58 +0800 Subject: [PATCH 3/5] init service card --- package-lock.json | 37 ++++++++++ package.json | 1 + src/components/ui/dialog.tsx | 120 +++++++++++++++++++++++++++++++++ src/components/xui/service.tsx | 108 +++++++++++++++++++++++++++++ src/routes/service.tsx | 8 +-- 5 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/xui/service.tsx diff --git a/package-lock.json b/package-lock.json index 82203c0..3e9659b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@hookform/resolvers": "^3.9.1", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^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", @@ -1259,6 +1260,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "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-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.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-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", diff --git a/package.json b/package.json index 03f2228..1b5dfd5 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@hookform/resolvers": "^3.9.1", "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^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", diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..c23630e --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/src/components/xui/service.tsx b/src/components/xui/service.tsx new file mode 100644 index 0000000..8d682bc --- /dev/null +++ b/src/components/xui/service.tsx @@ -0,0 +1,108 @@ +import { Plus } from "lucide-react" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { useForm } from "react-hook-form" +import { z } from "zod" +import { zodResolver } from "@hookform/resolvers/zod" + +interface ServiceCardProps { + className?: string; +} + +const formSchema = z.object({ + name: z.string(), + target: z.string(), + type: z.number().default(1), + enableShowInService: z.boolean().default(false), + duration: z.number().min(30), +}) + +export const ServiceCard: React.FC = ({ className }) => { + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + type: 1, + enableShowInService: false, + }, + }) + + function onSubmit(values: z.infer) { + + } + + return ( + + + + + + + New Service + {/* + + Anyone who has this link will be able to view this. + + */} + +
+
+ + ( + + Service Name + + + + + + )} + /> + ( + + Password + + + + + + )} + /> + + +
+ + + + + +
+
+ ) +} diff --git a/src/routes/service.tsx b/src/routes/service.tsx index c78c8fc..72d1f6f 100644 --- a/src/routes/service.tsx +++ b/src/routes/service.tsx @@ -1,6 +1,6 @@ import { swrFetcher } from "@/api/api" import { Checkbox } from "@/components/ui/checkbox" -import { Button } from "@/components/ui/button" +import { ServiceCard } from "@/components/xui/service" import { Plus } from "lucide-react" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { ModelService as Service } from "@/types" @@ -71,9 +71,7 @@ export default function ServicePage() {

Service

- +
@@ -117,5 +115,5 @@ export default function ServicePage() { )}
-
+
} \ No newline at end of file From 08560316a0132ea9aa57fbb896f2f149f094638e Mon Sep 17 00:00:00 2001 From: uubulb Date: Fri, 15 Nov 2024 18:53:55 +0800 Subject: [PATCH 4/5] add select --- package-lock.json | 50 +++++++++++ package.json | 1 + src/components/ui/select.tsx | 158 +++++++++++++++++++++++++++++++++ src/components/xui/service.tsx | 57 +++++++++--- src/routes/service.tsx | 3 +- 5 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index 3e9659b..e4d5dc2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@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-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", @@ -1104,6 +1105,12 @@ "node": ">=14" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", @@ -1688,6 +1695,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz", + "integrity": "sha512-rZJtWmorC7dFRi0owDmoijm6nSJH1tVw64QGiNIZ9PNLyBDtG+iAq+XGsya052At4BfarzY/Dhv9wrrUr6IMZA==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.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-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", diff --git a/package.json b/package.json index 1b5dfd5..513cb0c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@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-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", "@tanstack/react-table": "^8.20.5", "class-variance-authority": "^0.7.0", diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..fe56d4d --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,158 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props} + > + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/src/components/xui/service.tsx b/src/components/xui/service.tsx index 8d682bc..5a103d3 100644 --- a/src/components/xui/service.tsx +++ b/src/components/xui/service.tsx @@ -10,6 +10,13 @@ import { DialogTrigger, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { Form, FormControl, @@ -21,25 +28,36 @@ import { import { useForm } from "react-hook-form" import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" +import { ModelService } from "@/types" interface ServiceCardProps { className?: string; + data?: ModelService; } const formSchema = z.object({ name: z.string(), target: z.string(), - type: z.number().default(1), + type: z.number(), enableShowInService: z.boolean().default(false), duration: z.number().min(30), }) -export const ServiceCard: React.FC = ({ className }) => { +const monitorTypes = { + 1: "HTTP GET (Certificate expiration and changes)", + 2: "ICMP Ping", + 3: "TCPing", +} + +export const ServiceCard: React.FC = ({ className, data }) => { const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - type: 1, - enableShowInService: false, + name: data?.name, + target: data?.target, + type: data?.type, + enableShowInService: data?.enable_show_in_service, + duration: data?.duration, }, }) @@ -57,11 +75,6 @@ export const ServiceCard: React.FC = ({ className }) => { New Service - {/* - - Anyone who has this link will be able to view this. - - */}
@@ -84,7 +97,7 @@ export const ServiceCard: React.FC = ({ className }) => { name="target" render={({ field }) => ( - Password + Target @@ -92,6 +105,30 @@ export const ServiceCard: React.FC = ({ className }) => { )} /> + ( + + Type + + + + )} + />
diff --git a/src/routes/service.tsx b/src/routes/service.tsx index 72d1f6f..5bb324f 100644 --- a/src/routes/service.tsx +++ b/src/routes/service.tsx @@ -1,7 +1,6 @@ import { swrFetcher } from "@/api/api" import { Checkbox } from "@/components/ui/checkbox" import { ServiceCard } from "@/components/xui/service" -import { Plus } from "lucide-react" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { ModelService as Service } from "@/types" import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" @@ -71,7 +70,7 @@ export default function ServicePage() {

Service

- +
From b1a9a231a7116aee4743d4138547ee3b7bf7f74a Mon Sep 17 00:00:00 2001 From: uubulb Date: Fri, 15 Nov 2024 21:28:48 +0800 Subject: [PATCH 5/5] fix api types, add more form fields --- src/api/api.ts | 2 +- src/api/service.ts | 14 + src/components/{xui => }/service.tsx | 108 ++++- src/routes/service.tsx | 2 +- src/types/api.ts | 660 +++++++++++++-------------- 5 files changed, 432 insertions(+), 354 deletions(-) create mode 100644 src/api/service.ts rename src/components/{xui => }/service.tsx (51%) diff --git a/src/api/api.ts b/src/api/api.ts index 592ec00..0c23452 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -58,4 +58,4 @@ export async function fetcher(method: FetcherMethod, path: string, data?: any 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/api/service.ts b/src/api/service.ts new file mode 100644 index 0000000..1b917d3 --- /dev/null +++ b/src/api/service.ts @@ -0,0 +1,14 @@ +import { ModelServiceForm } from "@/types" +import { fetcher, FetcherMethod } from "./api" + +export const createService = async (data: ModelServiceForm): Promise => { + return fetcher(FetcherMethod.POST, '/api/v1/profile', data) +} + +export const updateService = async (id: number, data: ModelServiceForm): Promise => { + return fetcher(FetcherMethod.PATCH, `/api/v1/profile/${id}`, data) +} + +export const deleteService = async (id: number[]): Promise => { + return fetcher(FetcherMethod.POST, '/api/v1/batch-delete/service', id) +} diff --git a/src/components/xui/service.tsx b/src/components/service.tsx similarity index 51% rename from src/components/xui/service.tsx rename to src/components/service.tsx index 5a103d3..b6d1344 100644 --- a/src/components/xui/service.tsx +++ b/src/components/service.tsx @@ -29,40 +29,53 @@ import { useForm } from "react-hook-form" import { z } from "zod" import { zodResolver } from "@hookform/resolvers/zod" import { ModelService } from "@/types" +import { createService, updateService } from "@/api/service" +import { Checkbox } from "./ui/checkbox" +import { Label } from "./ui/label" interface ServiceCardProps { className?: string; data?: ModelService; } -const formSchema = z.object({ +const serviceFormSchema = z.object({ + cover: z.number(), + duration: z.number().min(30), + enable_show_in_service: z.boolean().default(false), + enable_trigger_task: z.boolean().default(false), + fail_trigger_tasks: z.array(z.number()), + latency_notify: z.boolean(), + max_latency: z.number(), + min_latency: z.number(), name: z.string(), + notification_group_id: z.number(), + notify: z.boolean(), + recover_trigger_tasks: z.array(z.number()), + skip_servers: z.record(z.boolean()), target: z.string(), type: z.number(), - enableShowInService: z.boolean().default(false), - duration: z.number().min(30), -}) +}); -const monitorTypes = { +const serviceTypes = { 1: "HTTP GET (Certificate expiration and changes)", 2: "ICMP Ping", 3: "TCPing", } +const serviceCoverageTypes = { + 0: "All excludes specific servers", + 1: "Only specific servers", +} + export const ServiceCard: React.FC = ({ className, data }) => { - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: data?.name, - target: data?.target, - type: data?.type, - enableShowInService: data?.enable_show_in_service, - duration: data?.duration, - }, + const form = useForm>({ + resolver: zodResolver(serviceFormSchema), + defaultValues: data, }) - function onSubmit(values: z.infer) { - + const onSubmit = (values: z.infer) => { + data?.id ? updateService(data.id, values) + : createService(values); } return ( @@ -78,7 +91,7 @@ export const ServiceCard: React.FC = ({ className, data }) =>
- + = ({ className, data }) => Target - + @@ -118,10 +131,61 @@ export const ServiceCard: React.FC = ({ className, data }) => - {Object.entries(monitorTypes).map(([k, v]) => ( - - {v} - + {Object.entries(serviceTypes).map(([k, v]) => ( + {v} + ))} + + + + + )} + /> + ( + + +
+ + +
+
+ +
+ )} + /> + ( + + Interval + + + + + + )} + /> + ( + + Coverage + diff --git a/src/routes/service.tsx b/src/routes/service.tsx index 5bb324f..a209979 100644 --- a/src/routes/service.tsx +++ b/src/routes/service.tsx @@ -1,6 +1,6 @@ import { swrFetcher } from "@/api/api" import { Checkbox } from "@/components/ui/checkbox" -import { ServiceCard } from "@/components/xui/service" +import { ServiceCard } from "@/components/service" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { ModelService as Service } from "@/types" import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table" diff --git a/src/types/api.ts b/src/types/api.ts index b20aa27..5994152 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -10,111 +10,111 @@ */ export interface GithubComNaibaNezhaModelCommonResponseAny { - data?: any; - error?: string; - success?: boolean; + data: any; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelAlertRule { - data?: ModelAlertRule[]; - error?: string; - success?: boolean; + data: ModelAlertRule[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelCron { - data?: ModelCron[]; - error?: string; - success?: boolean; + data: ModelCron[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelDDNSProfile { - data?: ModelDDNSProfile[]; - error?: string; - success?: boolean; + data: ModelDDNSProfile[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelNAT { - data?: ModelNAT[]; - error?: string; - success?: boolean; + data: ModelNAT[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelNotification { - data?: ModelNotification[]; - error?: string; - success?: boolean; + data: ModelNotification[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelNotificationGroupResponseItem { - data?: ModelNotificationGroupResponseItem[]; - error?: string; - success?: boolean; + data: ModelNotificationGroupResponseItem[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelServer { - data?: ModelServer[]; - error?: string; - success?: boolean; + data: ModelServer[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelServerGroupResponseItem { - data?: ModelServerGroupResponseItem[]; - error?: string; - success?: boolean; + data: ModelServerGroupResponseItem[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelServiceInfos { - data?: ModelServiceInfos[]; - error?: string; - success?: boolean; + data: ModelServiceInfos[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayModelUser { - data?: ModelUser[]; - error?: string; - success?: boolean; + data: ModelUser[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayString { - data?: string[]; - error?: string; - success?: boolean; + data: string[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseArrayUint64 { - data?: number[]; - error?: string; - success?: boolean; + data: number[]; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseModelConfig { - data?: ModelConfig; - error?: string; - success?: boolean; + data: ModelConfig; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseModelLoginResponse { - data?: ModelLoginResponse; - error?: string; - success?: boolean; + data: ModelLoginResponse; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseModelServiceResponse { - data?: ModelServiceResponse; - error?: string; - success?: boolean; + data: ModelServiceResponse; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseModelUser { - data?: ModelUser; - error?: string; - success?: boolean; + data: ModelUser; + error: string; + success: boolean; } export interface GithubComNaibaNezhaModelCommonResponseUint64 { - data?: number; - error?: string; - success?: boolean; + data: number; + error: string; + success: boolean; } export interface GormDeletedAt { @@ -124,111 +124,111 @@ export interface GormDeletedAt { } export interface ModelAlertRule { - created_at?: string; - deleted_at?: GormDeletedAt; - enable?: boolean; + created_at: string; + deleted_at: GormDeletedAt; + enable: boolean; /** 失败时执行的触发任务id */ - fail_trigger_tasks?: number[]; - id?: number; - name?: string; + fail_trigger_tasks: number[]; + id: number; + name: string; /** 该报警规则所在的通知组 */ - notification_group_id?: number; + notification_group_id: number; /** 恢复时执行的触发任务id */ - recover_trigger_tasks?: number[]; - rules?: ModelRule[]; + recover_trigger_tasks: number[]; + rules: ModelRule[]; /** 触发模式: 0-始终触发(默认) 1-单次触发 */ - trigger_mode?: number; - updated_at?: string; + trigger_mode: number; + updated_at: string; } export interface ModelAlertRuleForm { - enable?: boolean; + enable: boolean; /** 失败时触发的任务id */ - fail_trigger_tasks?: number[]; - name?: string; - notification_group_id?: number; + fail_trigger_tasks: number[]; + name: string; + notification_group_id: number; /** 恢复时触发的任务id */ - recover_trigger_tasks?: number[]; - rules?: ModelRule[]; - trigger_mode?: number; + recover_trigger_tasks: number[]; + rules: ModelRule[]; + trigger_mode: number; } export interface ModelConfig { - agent_secret_key?: string; - avg_ping_count?: number; + agent_secret_key: string; + avg_ping_count: number; /** 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;) */ - cover?: number; - custom_code?: string; - custom_code_dashboard?: string; + cover: number; + custom_code: string; + custom_code_dashboard: string; /** debug模式开关 */ - debug?: boolean; - dns_servers?: string; + debug: boolean; + dns_servers: string; /** IP变更提醒 */ - enable_ip_change_notification?: boolean; + enable_ip_change_notification: boolean; /** 通知信息IP不打码 */ - enable_plain_ip_in_notification?: boolean; + enable_plain_ip_in_notification: boolean; /** 特定服务器IP(多个服务器用逗号分隔) */ - ignored_ip_notification?: string; + ignored_ip_notification: string; /** [ServerID] -> bool(值为true代表当前ServerID在特定服务器列表内) */ - ignored_ip_notification_server_ids?: Record; - install_host?: string; - ip_change_notification_group_id?: number; - jwt_secret_key?: string; + ignored_ip_notification_server_ids: Record; + install_host: string; + ip_change_notification_group_id: number; + jwt_secret_key: string; /** 系统语言,默认 zh_CN */ - language?: string; - listen_port?: number; + language: string; + listen_port: number; /** 时区,默认为 Asia/Shanghai */ - location?: string; - site_name?: string; - tls?: boolean; + location: string; + site_name: string; + tls: boolean; } export interface ModelCreateFMResponse { - session_id?: string; + session_id: string; } export interface ModelCreateTerminalResponse { - server_id?: number; - server_name?: string; - session_id?: string; + server_id: number; + server_name: string; + session_id: string; } export interface ModelCron { - command?: string; + command: string; /** 计划任务覆盖范围 (0:仅覆盖特定服务器 1:仅忽略特定服务器 2:由触发该计划任务的服务器执行) */ - cover?: number; - created_at?: string; - cron_job_id?: number; - deleted_at?: GormDeletedAt; - id?: number; + cover: number; + created_at: string; + cron_job_id: number; + deleted_at: GormDeletedAt; + id: number; /** 最后一次执行时间 */ - last_executed_at?: string; + last_executed_at: string; /** 最后一次执行结果 */ - last_result?: boolean; - name?: string; + last_result: boolean; + name: string; /** 指定通知方式的分组 */ - notification_group_id?: number; + notification_group_id: number; /** 推送成功的通知 */ - push_successful?: boolean; + push_successful: boolean; /** 分钟 小时 天 月 星期 */ - scheduler?: string; - servers?: number[]; + scheduler: string; + servers: number[]; /** 0:计划任务 1:触发任务 */ - task_type?: number; - updated_at?: string; + task_type: number; + updated_at: string; } export interface ModelCronForm { - command?: string; - cover?: number; - id?: number; - name?: string; - notification_group_id?: number; - push_successful?: boolean; - scheduler?: string; - servers?: number[]; + command: string; + cover: number; + id: number; + name: string; + notification_group_id: number; + push_successful: boolean; + scheduler: string; + servers: number[]; /** 0:计划任务 1:触发任务 */ - task_type?: number; + task_type: number; } export interface ModelCycleTransferStats { @@ -243,171 +243,171 @@ export interface ModelCycleTransferStats { } export interface ModelDDNSForm { - access_id?: string; - access_secret?: string; - domains?: string[]; - enable_ipv4?: boolean; - enable_ipv6?: boolean; - max_retries?: number; - name?: string; - provider?: string; - webhook_headers?: string; - webhook_method?: number; - webhook_request_body?: string; - webhook_request_type?: number; - webhook_url?: string; + access_id: string; + access_secret: string; + domains: string[]; + enable_ipv4: boolean; + enable_ipv6: boolean; + max_retries: number; + name: string; + provider: string; + webhook_headers: string; + webhook_method: number; + webhook_request_body: string; + webhook_request_type: number; + webhook_url: string; } export interface ModelDDNSProfile { - access_id?: string; - access_secret?: string; - created_at?: string; - deleted_at?: GormDeletedAt; - domains?: string[]; - enable_ipv4?: boolean; - enable_ipv6?: boolean; - id?: number; - max_retries?: number; - name?: string; - provider?: string; - updated_at?: string; - webhook_headers?: string; - webhook_method?: number; - webhook_request_body?: string; - webhook_request_type?: number; - webhook_url?: string; + access_id: string; + access_secret: string; + created_at: string; + deleted_at: GormDeletedAt; + domains: string[]; + enable_ipv4: boolean; + enable_ipv6: boolean; + id: number; + max_retries: number; + name: string; + provider: string; + updated_at: string; + webhook_headers: string; + webhook_method: number; + webhook_request_body: string; + webhook_request_type: number; + webhook_url: string; } export interface ModelHost { - arch?: string; - boot_time?: number; - country_code?: string; - cpu?: string[]; - disk_total?: number; - gpu?: string[]; - ip?: string; - mem_total?: number; - platform?: string; - platform_version?: string; - swap_total?: number; - version?: string; - virtualization?: string; + arch: string; + boot_time: number; + country_code: string; + cpu: string[]; + disk_total: number; + gpu: string[]; + ip: string; + mem_total: number; + platform: string; + platform_version: string; + swap_total: number; + version: string; + virtualization: string; } export interface ModelHostState { - cpu?: number; - disk_used?: number; - gpu?: number[]; - load_1?: number; - load_15?: number; - load_5?: number; - mem_used?: number; - net_in_speed?: number; - net_in_transfer?: number; - net_out_speed?: number; - net_out_transfer?: number; - process_count?: number; - swap_used?: number; - tcp_conn_count?: number; - temperatures?: ModelSensorTemperature[]; - udp_conn_count?: number; - uptime?: number; + cpu: number; + disk_used: number; + gpu: number[]; + load_1: number; + load_15: number; + load_5: number; + mem_used: number; + net_in_speed: number; + net_in_transfer: number; + net_out_speed: number; + net_out_transfer: number; + process_count: number; + swap_used: number; + tcp_conn_count: number; + temperatures: ModelSensorTemperature[]; + udp_conn_count: number; + uptime: number; } export interface ModelLoginRequest { - password?: string; - username?: string; + password: string; + username: string; } export interface ModelLoginResponse { - expire?: string; - token?: string; + expire: string; + token: string; } export interface ModelNAT { - created_at?: string; - deleted_at?: GormDeletedAt; - domain?: string; + created_at: string; + deleted_at: GormDeletedAt; + domain: string; host?: string; - id?: number; + id: number; name?: string; serverID?: number; - updated_at?: string; + updated_at: string; } export interface ModelNATForm { - domain?: string; - host?: string; - name?: string; - server_id?: number; + domain: string; + host: string; + name: string; + server_id: number; } export interface ModelNotification { - created_at?: string; - deleted_at?: GormDeletedAt; - id?: number; - name?: string; - request_body?: string; - request_header?: string; - request_method?: number; - request_type?: number; - updated_at?: string; - url?: string; - verify_tls?: boolean; + created_at: string; + deleted_at: GormDeletedAt; + id: number; + name: string; + request_body: string; + request_header: string; + request_method: number; + request_type: number; + updated_at: string; + url: string; + verify_tls: boolean; } export interface ModelNotificationForm { - name?: string; - request_body?: string; - request_header?: string; - request_method?: number; - request_type?: number; - skip_check?: boolean; - url?: string; - verify_tls?: boolean; + name: string; + request_body: string; + request_header: string; + request_method: number; + request_type: number; + skip_check: boolean; + url: string; + verify_tls: boolean; } export interface ModelNotificationGroup { - created_at?: string; - deleted_at?: GormDeletedAt; - id?: number; - name?: string; - updated_at?: string; + created_at: string; + deleted_at: GormDeletedAt; + id: number; + name: string; + updated_at: string; } export interface ModelNotificationGroupForm { - name?: string; - notifications?: number[]; + name: string; + notifications: number[]; } export interface ModelNotificationGroupResponseItem { - group?: ModelNotificationGroup; - notifications?: number[]; + group: ModelNotificationGroup; + notifications: number[]; } export interface ModelRule { /** 覆盖范围 RuleCoverAll/IgnoreAll */ - cover?: number; + cover: number; /** 流量统计周期 */ - cycle_interval?: number; + cycle_interval: number; /** 流量统计的开始时间 */ - cycle_start?: string; + cycle_start: string; /** 流量统计周期单位,默认hour,可选(hour, day, week, month, year) */ - cycle_unit?: string; + cycle_unit: string; /** 持续时间 (秒) */ - duration?: number; + duration: number; /** 覆盖范围的排除 */ - ignore?: Record; + ignore: Record; /** 最大阈值 (百分比、字节 kb ÷ 1024) */ - max?: number; + max: number; /** 最小阈值 (百分比、字节 kb ÷ 1024) */ - min?: number; + min: number; /** * 指标类型,cpu、memory、swap、disk、net_in_speed、net_out_speed * net_all_speed、transfer_in、transfer_out、transfer_all、offline * transfer_in_cycle、transfer_out_cycle、transfer_all_cycle */ - type?: string; + type: string; } export interface ModelSensorTemperature { @@ -416,113 +416,113 @@ export interface ModelSensorTemperature { } export interface ModelServer { - created_at?: string; + created_at: string; /** DDNS配置 */ - ddns_profiles?: number[]; - deleted_at?: GormDeletedAt; + ddns_profiles: number[]; + deleted_at: GormDeletedAt; /** 展示排序,越大越靠前 */ - display_index?: number; + display_index: number; /** 启用DDNS */ - enable_ddns?: boolean; + enable_ddns: boolean; /** 对游客隐藏 */ - hide_for_guest?: boolean; - host?: ModelHost; - id?: number; - last_active?: string; - name?: string; + hide_for_guest: boolean; + host: ModelHost; + id: number; + last_active: string; + name: string; /** 管理员可见备注 */ - note?: string; + note: string; /** 公开备注 */ - public_note?: string; - state?: ModelHostState; - updated_at?: string; - uuid?: string; + public_note: string; + state: ModelHostState; + updated_at: string; + uuid: string; } export interface ModelServerForm { /** DDNS配置 */ - ddns_profiles?: number[]; + ddns_profiles: number[]; /** 展示排序,越大越靠前 */ - display_index?: number; + display_index: number; /** 启用DDNS */ - enable_ddns?: boolean; + enable_ddns: boolean; /** 对游客隐藏 */ - hide_for_guest?: boolean; - name?: string; + hide_for_guest: boolean; + name: string; /** 管理员可见备注 */ - note?: string; + note: string; /** 公开备注 */ - public_note?: string; + public_note: string; } export interface ModelServerGroup { - created_at?: string; - deleted_at?: GormDeletedAt; - id?: number; - name?: string; - updated_at?: string; + created_at: string; + deleted_at: GormDeletedAt; + id: number; + name: string; + updated_at: string; } export interface ModelServerGroupForm { - name?: string; - servers?: number[]; + name: string; + servers: number[]; } export interface ModelServerGroupResponseItem { - group?: ModelServerGroup; - servers?: number[]; + group: ModelServerGroup; + servers: number[]; } export interface ModelService { - cover?: number; - created_at?: string; - deleted_at?: GormDeletedAt; - duration?: number; - enable_show_in_service?: boolean; - enable_trigger_task?: boolean; + cover: number; + created_at: string; + deleted_at: GormDeletedAt; + duration: number; + enable_show_in_service: boolean; + enable_trigger_task: boolean; /** 失败时执行的触发任务id */ - fail_trigger_tasks?: number[]; - id?: number; - latency_notify?: boolean; - max_latency?: number; - min_latency?: number; - name?: string; + fail_trigger_tasks: number[]; + id: number; + latency_notify: boolean; + max_latency: number; + min_latency: number; + name: string; /** 当前服务监控所属的通知组 ID */ - notification_group_id?: number; - notify?: boolean; + notification_group_id: number; + notify: boolean; /** 恢复时执行的触发任务id */ - recover_trigger_tasks?: number[]; - skip_servers?: Record; - target?: string; - type?: number; - updated_at?: string; + recover_trigger_tasks: number[]; + skip_servers: Record; + target: string; + type: number; + updated_at: string; } export interface ModelServiceForm { - cover?: number; - duration?: number; - enable_show_in_service?: boolean; - enable_trigger_task?: boolean; - fail_trigger_tasks?: number[]; - latency_notify?: boolean; - max_latency?: number; - min_latency?: number; - name?: string; - notification_group_id?: number; - notify?: boolean; - recover_trigger_tasks?: number[]; - skip_servers?: Record; - target?: string; - type?: number; + cover: number; + duration: number; + enable_show_in_service: boolean; + enable_trigger_task: boolean; + fail_trigger_tasks: number[]; + latency_notify: boolean; + max_latency: number; + min_latency: number; + name: string; + notification_group_id: number; + notify: boolean; + recover_trigger_tasks: number[]; + skip_servers: Record; + target: string; + type: number; } export interface ModelServiceInfos { - avg_delay?: number[]; - created_at?: number[]; - monitor_id?: number; - monitor_name?: string; - server_id?: number; - server_name?: string; + avg_delay: number[]; + created_at: number[]; + monitor_id: number; + monitor_name: string; + server_id: number; + server_name: string; } export interface ModelServiceResponse { @@ -542,52 +542,52 @@ export interface ModelServiceResponseItem { } export interface ModelSettingForm { - cover?: number; - custom_code?: string; - custom_code_dashboard?: string; - custom_nameservers?: string; - enable_ip_change_notification?: boolean; - enable_plain_ip_in_notification?: boolean; - ignored_ip_notification?: string; - install_host?: string; + cover: number; + custom_code: string; + custom_code_dashboard: string; + custom_nameservers: string; + enable_ip_change_notification: boolean; + enable_plain_ip_in_notification: boolean; + ignored_ip_notification: string; + install_host: string; /** IP变更提醒的通知组 */ - ip_change_notification_group_id?: number; - language?: string; - site_name?: string; + ip_change_notification_group_id: number; + language: string; + site_name: string; } export interface ModelStreamServer { /** 展示排序,越大越靠前 */ - display_index?: number; - host?: ModelHost; - id?: number; - last_active?: string; - name?: string; + display_index: number; + host: ModelHost; + id: number; + last_active: string; + name: string; /** 公开备注,只第一个数据包有值 */ - public_note?: string; - state?: ModelHostState; + public_note: string; + state: ModelHostState; } export interface ModelStreamServerData { - now?: number; - servers?: ModelStreamServer[]; + now: number; + servers: ModelStreamServer[]; } export interface ModelTerminalForm { - protocol?: string; - server_id?: number; + protocol: string; + server_id: number; } export interface ModelUser { - created_at?: string; - deleted_at?: GormDeletedAt; - id?: number; - password?: string; - updated_at?: string; - username?: string; + created_at: string; + deleted_at: GormDeletedAt; + id: number; + password: string; + updated_at: string; + username: string; } export interface ModelUserForm { - password?: string; - username?: string; + password: string; + username: string; }