mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 12:40:08 +00:00
feat: batch set server config (#114)
* feat: batch set server config * make every field optional * chore: auto-fix linting and formatting issues * update * [WIP] improve batch edit ux * chore: auto-fix linting and formatting issues
This commit is contained in:
96
package.json
96
package.json
@@ -13,68 +13,68 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.10.0",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.2",
|
"@radix-ui/react-alert-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-avatar": "^1.1.1",
|
"@radix-ui/react-avatar": "^1.1.2",
|
||||||
"@radix-ui/react-checkbox": "^1.1.2",
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.2",
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
"@radix-ui/react-label": "^2.1.0",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.1",
|
"@radix-ui/react-navigation-menu": "^1.2.4",
|
||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.1",
|
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||||
"@radix-ui/react-select": "^2.1.2",
|
"@radix-ui/react-select": "^2.1.5",
|
||||||
"@radix-ui/react-separator": "^1.1.0",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-tabs": "^1.1.1",
|
"@radix-ui/react-tabs": "^1.1.2",
|
||||||
"@tanstack/react-table": "^8.20.5",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@xterm/addon-attach": "^0.11.0",
|
"@xterm/addon-attach": "^0.11.0",
|
||||||
"@xterm/addon-fit": "^0.10.0",
|
"@xterm/addon-fit": "^0.10.0",
|
||||||
"@xterm/xterm": "^5.5.0",
|
"@xterm/xterm": "^5.5.0",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.4",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"framer-motion": "^11.14.1",
|
"framer-motion": "^11.18.2",
|
||||||
"i18next": "^24.0.2",
|
"i18next": "^24.2.2",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.2",
|
||||||
"jotai-zustand": "^0.6.0",
|
"jotai-zustand": "^0.6.0",
|
||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"react": "^18.3.1",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^19.0.0",
|
||||||
"react-hook-form": "^7.53.1",
|
"react-hook-form": "^7.54.2",
|
||||||
"react-i18next": "^15.1.2",
|
"react-i18next": "^15.4.0",
|
||||||
"react-router-dom": "^6.27.0",
|
"react-router-dom": "^7.1.5",
|
||||||
"react-virtuoso": "^4.12.0",
|
"react-virtuoso": "^4.12.3",
|
||||||
"sonner": "^1.6.1",
|
"sonner": "^1.7.4",
|
||||||
"swr": "^2.2.5",
|
"swr": "^2.3.0",
|
||||||
"tailwind-merge": "^2.5.4",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^1.1.1",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.24.1",
|
||||||
"zustand": "^5.0.1"
|
"zustand": "^5.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.13.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@types/node": "^22.8.6",
|
"@types/node": "^22.13.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.18",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.5",
|
||||||
"@vitejs/plugin-react": "^4.3.3",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react-hooks": "^5.0.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.14",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"globals": "^15.11.0",
|
"globals": "^15.14.0",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.5.1",
|
||||||
"swagger-typescript-api": "^13.0.22",
|
"swagger-typescript-api": "^13.0.23",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.3",
|
||||||
"typescript-eslint": "^8.11.0",
|
"typescript-eslint": "^8.22.0",
|
||||||
"vite": "^5.4.10"
|
"vite": "^6.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { ModelForceUpdateResponse, ModelServer, ModelServerForm } from "@/types"
|
import {
|
||||||
|
ModelServer,
|
||||||
|
ModelServerConfigForm,
|
||||||
|
ModelServerForm,
|
||||||
|
ModelServerTaskResponse,
|
||||||
|
} from "@/types"
|
||||||
|
|
||||||
import { FetcherMethod, fetcher } from "./api"
|
import { FetcherMethod, fetcher } from "./api"
|
||||||
|
|
||||||
@@ -10,8 +15,8 @@ export const deleteServer = async (id: number[]): Promise<void> => {
|
|||||||
return fetcher<void>(FetcherMethod.POST, "/api/v1/batch-delete/server", id)
|
return fetcher<void>(FetcherMethod.POST, "/api/v1/batch-delete/server", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const forceUpdateServer = async (id: number[]): Promise<ModelForceUpdateResponse> => {
|
export const forceUpdateServer = async (id: number[]): Promise<ModelServerTaskResponse> => {
|
||||||
return fetcher<ModelForceUpdateResponse>(FetcherMethod.POST, "/api/v1/force-update/server", id)
|
return fetcher<ModelServerTaskResponse>(FetcherMethod.POST, "/api/v1/force-update/server", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getServers = async (): Promise<ModelServer[]> => {
|
export const getServers = async (): Promise<ModelServer[]> => {
|
||||||
@@ -19,9 +24,11 @@ export const getServers = async (): Promise<ModelServer[]> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getServerConfig = async (id: number): Promise<string> => {
|
export const getServerConfig = async (id: number): Promise<string> => {
|
||||||
return fetcher<string>(FetcherMethod.GET, `/api/v1/server/${id}/config`, null)
|
return fetcher<string>(FetcherMethod.GET, `/api/v1/server/config/${id}`, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setServerConfig = async (id: number, data: string): Promise<void> => {
|
export const setServerConfig = async (
|
||||||
return fetcher<void>(FetcherMethod.POST, `/api/v1/server/${id}/config`, data)
|
data: ModelServerConfigForm,
|
||||||
|
): Promise<ModelServerTaskResponse> => {
|
||||||
|
return fetcher<ModelServerTaskResponse>(FetcherMethod.POST, `/api/v1/server/config`, data)
|
||||||
}
|
}
|
||||||
|
|||||||
104
src/components/server-config-batch.tsx
Normal file
104
src/components/server-config-batch.tsx
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import { setServerConfig } from "@/api/server"
|
||||||
|
import { Button, ButtonProps } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
|
import { IconButton } from "@/components/xui/icon-button"
|
||||||
|
import { ModelServerTaskResponse } from "@/types"
|
||||||
|
import { useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
import { Pusher } from "./xui/pusher"
|
||||||
|
|
||||||
|
interface ServerConfigCardBatchProps extends ButtonProps {
|
||||||
|
sid: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerConfigCardBatch: React.FC<ServerConfigCardBatchProps> = ({ sid, ...props }) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const [data, setData] = useState<Record<string, any>>({})
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [currentKey, setCurrentKey] = useState<string>("")
|
||||||
|
const [currentVal, setCurrentVal] = useState<string>("")
|
||||||
|
|
||||||
|
const onSubmit = async () => {
|
||||||
|
let resp: ModelServerTaskResponse = {}
|
||||||
|
try {
|
||||||
|
resp = await setServerConfig({ config: JSON.stringify(data), servers: sid })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
toast(t("Error"), {
|
||||||
|
description: t("Results.UnExpectedError"),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
toast(t("Done"), {
|
||||||
|
description:
|
||||||
|
t("Results.ForceUpdate") +
|
||||||
|
(resp.success?.length ? t(`Success`) + ` [${resp.success.join(",")}]` : "") +
|
||||||
|
(resp.failure?.length ? t(`Failure`) + ` [${resp.failure.join(",")}]` : "") +
|
||||||
|
(resp.offline?.length ? t(`Offline`) + ` [${resp.offline.join(",")}]` : ""),
|
||||||
|
})
|
||||||
|
setOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<IconButton {...props} icon="cog" />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-xl">
|
||||||
|
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||||
|
<div className="items-center mx-1">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t("EditServerConfig")}</DialogTitle>
|
||||||
|
<DialogDescription />
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="flex flex-col gap-3 mt-4">
|
||||||
|
<Label>Option</Label>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="option"
|
||||||
|
value={currentKey}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCurrentKey(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Label>Value</Label>
|
||||||
|
<Textarea
|
||||||
|
className="resize-y"
|
||||||
|
value={currentVal}
|
||||||
|
onChange={(e) => {
|
||||||
|
setCurrentVal(e.target.value)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Pusher property={[currentKey, currentVal]} setData={setData} />
|
||||||
|
<DialogFooter className="justify-end">
|
||||||
|
<DialogClose asChild>
|
||||||
|
<Button type="button" className="my-2" variant="secondary">
|
||||||
|
{t("Close")}
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button type="submit" className="my-2" onClick={onSubmit}>
|
||||||
|
{t("Submit")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getServerConfig, setServerConfig } from "@/api/server"
|
import { getServerConfig, setServerConfig } from "@/api/server"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button, ButtonProps } from "@/components/ui/button"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@@ -25,6 +25,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"
|
|||||||
import { Textarea } from "@/components/ui/textarea"
|
import { Textarea } from "@/components/ui/textarea"
|
||||||
import { IconButton } from "@/components/xui/icon-button"
|
import { IconButton } from "@/components/xui/icon-button"
|
||||||
import { asOptionalField } from "@/lib/utils"
|
import { asOptionalField } from "@/lib/utils"
|
||||||
|
import { ModelServerTaskResponse } from "@/types"
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
@@ -56,7 +57,7 @@ const agentConfigSchema = z.object({
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ip_report_period: z.coerce.number().int().min(30),
|
ip_report_period: asOptionalField(z.coerce.number().int().min(30)),
|
||||||
nic_allowlist: asOptionalField(z.record(z.boolean())),
|
nic_allowlist: asOptionalField(z.record(z.boolean())),
|
||||||
nic_allowlist_raw: asOptionalField(
|
nic_allowlist_raw: asOptionalField(
|
||||||
z.string().refine(
|
z.string().refine(
|
||||||
@@ -73,7 +74,7 @@ const agentConfigSchema = z.object({
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
report_delay: z.coerce.number().int().min(1).max(4),
|
report_delay: asOptionalField(z.coerce.number().int().min(1).max(4)),
|
||||||
skip_connection_count: asOptionalField(z.boolean()),
|
skip_connection_count: asOptionalField(z.boolean()),
|
||||||
skip_procs_count: asOptionalField(z.boolean()),
|
skip_procs_count: asOptionalField(z.boolean()),
|
||||||
temperature: asOptionalField(z.boolean()),
|
temperature: asOptionalField(z.boolean()),
|
||||||
@@ -99,7 +100,11 @@ for (let i = 0; i < boolFields.length; i += 2) {
|
|||||||
groupedBoolFields.push(boolFields.slice(i, i + 2))
|
groupedBoolFields.push(boolFields.slice(i, i + 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ServerConfigCard = ({ id }: { id: number }) => {
|
interface ServerConfigCardProps extends ButtonProps {
|
||||||
|
sid: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ServerConfigCard = ({ sid, ...props }: ServerConfigCardProps) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [data, setData] = useState<AgentConfig | undefined>(undefined)
|
const [data, setData] = useState<AgentConfig | undefined>(undefined)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@@ -108,7 +113,11 @@ export const ServerConfigCard = ({ id }: { id: number }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await getServerConfig(id)
|
if (sid.length > 1) {
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const result = await getServerConfig(sid[0])
|
||||||
setData(JSON.parse(result))
|
setData(JSON.parse(result))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
@@ -151,6 +160,7 @@ export const ServerConfigCard = ({ id }: { id: number }) => {
|
|||||||
}, [data, form])
|
}, [data, form])
|
||||||
|
|
||||||
const onSubmit = async (values: AgentConfig) => {
|
const onSubmit = async (values: AgentConfig) => {
|
||||||
|
let resp: ModelServerTaskResponse = {}
|
||||||
try {
|
try {
|
||||||
values.nic_allowlist = values.nic_allowlist_raw
|
values.nic_allowlist = values.nic_allowlist_raw
|
||||||
? JSON.parse(values.nic_allowlist_raw)
|
? JSON.parse(values.nic_allowlist_raw)
|
||||||
@@ -158,7 +168,7 @@ export const ServerConfigCard = ({ id }: { id: number }) => {
|
|||||||
values.hard_drive_partition_allowlist = values.hard_drive_partition_allowlist_raw
|
values.hard_drive_partition_allowlist = values.hard_drive_partition_allowlist_raw
|
||||||
? JSON.parse(values.hard_drive_partition_allowlist_raw)
|
? JSON.parse(values.hard_drive_partition_allowlist_raw)
|
||||||
: undefined
|
: undefined
|
||||||
await setServerConfig(id, JSON.stringify(values))
|
resp = await setServerConfig({ config: JSON.stringify(values), servers: sid })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
toast(t("Error"), {
|
toast(t("Error"), {
|
||||||
@@ -166,14 +176,31 @@ export const ServerConfigCard = ({ id }: { id: number }) => {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
toast(t("Done"), {
|
||||||
|
description:
|
||||||
|
t("Results.ForceUpdate") +
|
||||||
|
(resp.success?.length ? t(`Success`) + ` [${resp.success.join(",")}]` : "") +
|
||||||
|
(resp.failure?.length ? t(`Failure`) + ` [${resp.failure.join(",")}]` : "") +
|
||||||
|
(resp.offline?.length ? t(`Offline`) + ` [${resp.offline.join(",")}]` : ""),
|
||||||
|
})
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
form.reset()
|
form.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return sid.length < 1 ? (
|
||||||
|
<IconButton
|
||||||
|
{...props}
|
||||||
|
icon="cog"
|
||||||
|
onClick={() => {
|
||||||
|
toast(t("Error"), {
|
||||||
|
description: t("Results.NoRowsAreSelected"),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<IconButton variant="outline" icon="cog" />
|
<IconButton {...props} icon="cog" />
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="sm:max-w-xl">
|
<DialogContent className="sm:max-w-xl">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
Expand,
|
Expand,
|
||||||
FolderClosed,
|
FolderClosed,
|
||||||
Menu,
|
Menu,
|
||||||
|
Minus,
|
||||||
Play,
|
Play,
|
||||||
Plus,
|
Plus,
|
||||||
Terminal,
|
Terminal,
|
||||||
@@ -35,6 +36,7 @@ export interface IconButtonProps extends ButtonProps {
|
|||||||
| "ban"
|
| "ban"
|
||||||
| "expand"
|
| "expand"
|
||||||
| "cog"
|
| "cog"
|
||||||
|
| "minus"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
||||||
@@ -92,6 +94,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
|
|||||||
case "cog": {
|
case "cog": {
|
||||||
return <CogIcon />
|
return <CogIcon />
|
||||||
}
|
}
|
||||||
|
case "minus": {
|
||||||
|
return <Minus />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
50
src/components/xui/pusher.tsx
Normal file
50
src/components/xui/pusher.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useState } from "react"
|
||||||
|
|
||||||
|
import { Label } from "../ui/label"
|
||||||
|
import { Textarea } from "../ui/textarea"
|
||||||
|
import { IconButton } from "./icon-button"
|
||||||
|
|
||||||
|
interface PusherProps {
|
||||||
|
property: [string, string]
|
||||||
|
setData: React.Dispatch<React.SetStateAction<Record<string, any>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Pusher: React.FC<PusherProps> = ({ property, setData }) => {
|
||||||
|
const [cData, setCData] = useState<Record<string, any>>({})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="flex gap-2 ml-auto">
|
||||||
|
<IconButton
|
||||||
|
icon="plus"
|
||||||
|
onClick={() => {
|
||||||
|
const [k, v] = property
|
||||||
|
if (k && v) {
|
||||||
|
const temp = { ...cData }
|
||||||
|
temp[k] = JSON.parse(v)
|
||||||
|
setCData(temp)
|
||||||
|
setData(cData)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
icon="minus"
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => {
|
||||||
|
const [k] = property
|
||||||
|
if (k) {
|
||||||
|
const temp = { ...cData }
|
||||||
|
temp[k] = undefined
|
||||||
|
setCData(temp)
|
||||||
|
setData(cData)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Label>Preview</Label>
|
||||||
|
<Textarea value={JSON.stringify(cData, null, 2)} readOnly />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { InstallCommandsMenu } from "@/components/install-commands"
|
|||||||
import { NoteMenu } from "@/components/note-menu"
|
import { NoteMenu } from "@/components/note-menu"
|
||||||
import { ServerCard } from "@/components/server"
|
import { ServerCard } from "@/components/server"
|
||||||
import { ServerConfigCard } from "@/components/server-config"
|
import { ServerConfigCard } from "@/components/server-config"
|
||||||
|
import { ServerConfigCardBatch } from "@/components/server-config-batch"
|
||||||
import { TerminalButton } from "@/components/terminal"
|
import { TerminalButton } from "@/components/terminal"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import {
|
import {
|
||||||
@@ -20,7 +21,7 @@ import {
|
|||||||
import { IconButton } from "@/components/xui/icon-button"
|
import { IconButton } from "@/components/xui/icon-button"
|
||||||
import { useServer } from "@/hooks/useServer"
|
import { useServer } from "@/hooks/useServer"
|
||||||
import { joinIP } from "@/lib/utils"
|
import { joinIP } from "@/lib/utils"
|
||||||
import { ModelForceUpdateResponse, ModelServer as Server } from "@/types"
|
import { ModelServerTaskResponse, ModelServer as Server } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
@@ -144,7 +145,7 @@ export default function ServerPage() {
|
|||||||
<>
|
<>
|
||||||
<TerminalButton id={s.id} />
|
<TerminalButton id={s.id} />
|
||||||
<ServerCard mutate={mutate} data={s} />
|
<ServerCard mutate={mutate} data={s} />
|
||||||
<ServerConfigCard id={s.id} />
|
<ServerConfigCard sid={[s.id]} variant="outline" />
|
||||||
</>
|
</>
|
||||||
</ActionButtonGroup>
|
</ActionButtonGroup>
|
||||||
)
|
)
|
||||||
@@ -187,7 +188,7 @@ export default function ServerPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let resp: ModelForceUpdateResponse = {}
|
let resp: ModelServerTaskResponse = {}
|
||||||
try {
|
try {
|
||||||
resp = await forceUpdateServer(id)
|
resp = await forceUpdateServer(id)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -212,6 +213,10 @@ export default function ServerPage() {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<ServerConfigCardBatch
|
||||||
|
sid={selectedRows.map((r) => r.original.id)}
|
||||||
|
className="shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] bg-yellow-600 text-white hover:bg-yellow-500 dark:hover:bg-yellow-700 rounded-lg"
|
||||||
|
/>
|
||||||
<InstallCommandsMenu className="shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] bg-blue-700 text-white hover:bg-blue-600 dark:hover:bg-blue-800 rounded-lg" />
|
<InstallCommandsMenu className="shadow-[inset_0_1px_0_rgba(255,255,255,0.2)] bg-blue-700 text-white hover:bg-blue-600 dark:hover:bg-blue-800 rounded-lg" />
|
||||||
</HeaderButtonGroup>
|
</HeaderButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -99,12 +99,6 @@ export interface GithubComNezhahqNezhaModelCommonResponseGithubComNezhahqNezhaMo
|
|||||||
success: boolean
|
success: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GithubComNezhahqNezhaModelCommonResponseModelForceUpdateResponse {
|
|
||||||
data: ModelForceUpdateResponse
|
|
||||||
error: string
|
|
||||||
success: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GithubComNezhahqNezhaModelCommonResponseModelLoginResponse {
|
export interface GithubComNezhahqNezhaModelCommonResponseModelLoginResponse {
|
||||||
data: ModelLoginResponse
|
data: ModelLoginResponse
|
||||||
error: string
|
error: string
|
||||||
@@ -117,6 +111,12 @@ export interface GithubComNezhahqNezhaModelCommonResponseModelProfile {
|
|||||||
success: boolean
|
success: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GithubComNezhahqNezhaModelCommonResponseModelServerTaskResponse {
|
||||||
|
data: ModelServerTaskResponse
|
||||||
|
error: string
|
||||||
|
success: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface GithubComNezhahqNezhaModelCommonResponseModelServiceResponse {
|
export interface GithubComNezhahqNezhaModelCommonResponseModelServiceResponse {
|
||||||
data: ModelServiceResponse
|
data: ModelServiceResponse
|
||||||
error: string
|
error: string
|
||||||
@@ -336,12 +336,6 @@ export interface ModelDDNSProfile {
|
|||||||
webhook_url: string
|
webhook_url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelForceUpdateResponse {
|
|
||||||
failure?: number[]
|
|
||||||
offline?: number[]
|
|
||||||
success?: number[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelFrontendTemplate {
|
export interface ModelFrontendTemplate {
|
||||||
author: string
|
author: string
|
||||||
is_admin: boolean
|
is_admin: boolean
|
||||||
@@ -577,6 +571,11 @@ export interface ModelServer {
|
|||||||
uuid: string
|
uuid: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelServerConfigForm {
|
||||||
|
config: string
|
||||||
|
servers: number[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModelServerForm {
|
export interface ModelServerForm {
|
||||||
/** DDNS配置 */
|
/** DDNS配置 */
|
||||||
ddns_profiles?: number[]
|
ddns_profiles?: number[]
|
||||||
@@ -615,6 +614,12 @@ export interface ModelServerGroupResponseItem {
|
|||||||
servers: number[]
|
servers: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelServerTaskResponse {
|
||||||
|
failure?: number[]
|
||||||
|
offline?: number[]
|
||||||
|
success?: number[]
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModelService {
|
export interface ModelService {
|
||||||
cover: number
|
cover: number
|
||||||
created_at: string
|
created_at: string
|
||||||
|
|||||||
69
src/types/server.ts
Normal file
69
src/types/server.ts
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { asOptionalField } from "@/lib/utils"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const AgentConfigSchema = z.object({
|
||||||
|
debug: asOptionalField(z.boolean()),
|
||||||
|
disable_auto_update: asOptionalField(z.boolean()),
|
||||||
|
disable_command_execute: asOptionalField(z.boolean()),
|
||||||
|
disable_force_update: asOptionalField(z.boolean()),
|
||||||
|
disable_nat: asOptionalField(z.boolean()),
|
||||||
|
disable_send_query: asOptionalField(z.boolean()),
|
||||||
|
gpu: asOptionalField(z.boolean()),
|
||||||
|
hard_drive_partition_allowlist: asOptionalField(z.array(z.string())),
|
||||||
|
hard_drive_partition_allowlist_raw: asOptionalField(
|
||||||
|
z.string().refine(
|
||||||
|
(val) => {
|
||||||
|
try {
|
||||||
|
JSON.parse(val)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Invalid JSON string",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ip_report_period: asOptionalField(z.coerce.number().int().min(30)),
|
||||||
|
nic_allowlist: asOptionalField(z.record(z.boolean())),
|
||||||
|
nic_allowlist_raw: asOptionalField(
|
||||||
|
z.string().refine(
|
||||||
|
(val) => {
|
||||||
|
try {
|
||||||
|
JSON.parse(val)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Invalid JSON string",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
report_delay: asOptionalField(z.coerce.number().int().min(1).max(4)),
|
||||||
|
skip_connection_count: asOptionalField(z.boolean()),
|
||||||
|
skip_procs_count: asOptionalField(z.boolean()),
|
||||||
|
temperature: asOptionalField(z.boolean()),
|
||||||
|
})
|
||||||
|
|
||||||
|
type AgentConfig = z.infer<typeof AgentConfigSchema>
|
||||||
|
|
||||||
|
const boolFields: (keyof AgentConfig)[] = [
|
||||||
|
"disable_auto_update",
|
||||||
|
"disable_command_execute",
|
||||||
|
"disable_force_update",
|
||||||
|
"disable_nat",
|
||||||
|
"disable_send_query",
|
||||||
|
"gpu",
|
||||||
|
"temperature",
|
||||||
|
"skip_connection_count",
|
||||||
|
"skip_procs_count",
|
||||||
|
"debug",
|
||||||
|
]
|
||||||
|
|
||||||
|
export const GroupedBoolFields: (keyof AgentConfig)[][] = []
|
||||||
|
for (let i = 0; i < boolFields.length; i += 2) {
|
||||||
|
GroupedBoolFields.push(boolFields.slice(i, i + 2))
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user