Merge pull request #2 from uubulb/dev

init service page
This commit is contained in:
naiba
2024-11-15 23:06:26 +08:00
committed by GitHub
12 changed files with 1048 additions and 335 deletions

87
package-lock.json generated
View File

@@ -11,9 +11,11 @@
"@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",
"@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",
@@ -1103,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",
@@ -1259,6 +1267,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",
@@ -1651,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",

View File

@@ -13,9 +13,11 @@
"@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",
"@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",

View File

@@ -58,4 +58,4 @@ export async function fetcher<T>(method: FetcherMethod, path: string, data?: any
export async function swrFetcher<T>(input: string | URL | globalThis.Request, init?: RequestInit) {
return fetcher<T>(init?.method as FetcherMethod, input.toString(), init?.body);
}
}

14
src/api/service.ts Normal file
View File

@@ -0,0 +1,14 @@
import { ModelServiceForm } from "@/types"
import { fetcher, FetcherMethod } from "./api"
export const createService = async (data: ModelServiceForm): Promise<number> => {
return fetcher<number>(FetcherMethod.POST, '/api/v1/profile', data)
}
export const updateService = async (id: number, data: ModelServiceForm): Promise<void> => {
return fetcher<void>(FetcherMethod.PATCH, `/api/v1/profile/${id}`, data)
}
export const deleteService = async (id: number[]): Promise<void> => {
return fetcher<void>(FetcherMethod.POST, '/api/v1/batch-delete/service', id)
}

209
src/components/service.tsx Normal file
View File

@@ -0,0 +1,209 @@
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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
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"
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 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(),
});
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<ServiceCardProps> = ({ className, data }) => {
const form = useForm<z.infer<typeof serviceFormSchema>>({
resolver: zodResolver(serviceFormSchema),
defaultValues: data,
})
const onSubmit = (values: z.infer<typeof serviceFormSchema>) => {
data?.id ? updateService(data.id, values)
: createService(values);
}
return (
<Dialog>
<DialogTrigger asChild>
<Button className={`${className}`}>
<Plus /> Add New Service
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>New Service</DialogTitle>
</DialogHeader>
<div className="items-center">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Service Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="target"
render={({ field }) => (
<FormItem>
<FormLabel>Target</FormLabel>
<FormControl>
<Input type="link" placeholder="HTTP (https://t.tt)Ping (t.tt)TCP (t.tt:80)" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="type"
render={({ field }) => (
<FormItem>
<FormLabel>Type</FormLabel>
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select service type" />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.entries(serviceTypes).map(([k, v]) => (
<SelectItem value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="enable_show_in_service"
render={({ field }) => (
<FormItem className="flex items-center space-x-2">
<FormControl>
<div className="flex items-center gap-2">
<Checkbox
checked={field.value}
onCheckedChange={field.onChange}
/>
<Label className="text-sm">Show in Service</Label>
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="duration"
render={({ field }) => (
<FormItem>
<FormLabel>Interval</FormLabel>
<FormControl>
<Input type="number" placeholder="Seconds" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="cover"
render={({ field }) => (
<FormItem>
<FormLabel>Coverage</FormLabel>
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select service type" />
</SelectTrigger>
</FormControl>
<SelectContent>
{Object.entries(serviceCoverageTypes).map(([k, v]) => (
<SelectItem value={k}>{v}</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</div>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
)
}

View File

@@ -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<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -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<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
))
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
"flex cursor-default items-center justify-center py-1",
className
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
))
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
}

View File

@@ -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'

View File

@@ -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: <ServerPage />,
},
{
path: "/dashboard/service",
element: <ServicePage />,
},
]
},
]);

View File

@@ -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",
@@ -65,7 +65,7 @@ export default function ServerPage() {
})
return <div className="px-9">
<div className="flex space-between mt-4">
<div className="flex space-between mt-4 pb-4">
<h1 className="text-3xl font-bold tracking-tight">
Server
</h1>

118
src/routes/service.tsx Normal file
View File

@@ -0,0 +1,118 @@
import { swrFetcher } from "@/api/api"
import { Checkbox } from "@/components/ui/checkbox"
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"
import useSWR from "swr"
export default function ServicePage() {
const columns: ColumnDef<Service>[] = [
{
id: "select",
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => 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<Service[]>('/api/v1/service', swrFetcher)
const table = useReactTable({
data: data ?? [],
columns,
getCoreRowModel: getCoreRowModel(),
})
return <div className="px-9">
<div className="flex space-between mt-4 pb-4">
<h1 className="text-3xl font-bold tracking-tight">
Service
</h1>
<ServiceCard className="ml-auto" />
</div>
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</TableHead>
)
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div >
}

View File

@@ -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<string, boolean>;
install_host?: string;
ip_change_notification_group_id?: number;
jwt_secret_key?: string;
ignored_ip_notification_server_ids: Record<string, boolean>;
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<string, boolean>;
ignore: Record<string, boolean>;
/** 最大阈值 (百分比、字节 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<string, boolean>;
target?: string;
type?: number;
updated_at?: string;
recover_trigger_tasks: number[];
skip_servers: Record<string, boolean>;
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<string, boolean>;
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<string, boolean>;
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;
}