feat: 批量转移服务器给其他用户

This commit is contained in:
naiba
2025-06-16 23:44:30 +08:00
parent bf7c42e4e7
commit 5f854c3dd0
6 changed files with 141 additions and 19 deletions

View File

@@ -1,4 +1,5 @@
import {
ModelBatchMoveServerForm,
ModelServer,
ModelServerConfigForm,
ModelServerForm,
@@ -15,6 +16,10 @@ export const deleteServer = async (id: number[]): Promise<void> => {
return fetcher<void>(FetcherMethod.POST, "/api/v1/batch-delete/server", id)
}
export const batchMoveServer = async (data: ModelBatchMoveServerForm): Promise<void> => {
return fetcher<void>(FetcherMethod.POST, "/api/v1/batch-move/server", data)
}
export const forceUpdateServer = async (id: number[]): Promise<ModelServerTaskResponse> => {
return fetcher<ModelServerTaskResponse>(FetcherMethod.POST, "/api/v1/force-update/server", id)
}

View File

@@ -0,0 +1,100 @@
import { batchMoveServer } 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 { IconButton } from "@/components/xui/icon-button"
import { useState } from "react"
import { useTranslation } from "react-i18next"
import { toast } from "sonner"
import { Textarea } from "./ui/textarea"
interface BatchMoveServerIconProps extends ButtonProps {
serverIds: number[]
}
export const BatchMoveServerIcon: React.FC<BatchMoveServerIconProps> = ({ serverIds, ...props }) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const [toUserId, setToUserId] = useState<number | undefined>(undefined)
const onSubmit = async () => {
try {
await batchMoveServer({
ids: serverIds,
to_user: toUserId!
})
} catch (e) {
console.error(e)
toast(t("Error"), {
description: t("Results.UnExpectedError"),
})
return
}
toast(t("Done"))
setOpen(false)
}
return serverIds.length < 1 ? (
<IconButton
{...props}
icon="user-pen"
onClick={() => {
toast(t("Error"), {
description: t("Results.NoRowsAreSelected"),
})
}}
/>
) : (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<IconButton {...props} icon="user-pen" />
</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("BatchMoveServer")}</DialogTitle>
<DialogDescription />
</DialogHeader>
<div className="flex flex-col gap-3 mt-4">
<Label>{t("Servers")}</Label>
<Textarea disabled>
{serverIds.join(", ")}
</Textarea>
<Label>{t("ToUser")}</Label>
<Input
type="number"
placeholder="User ID"
value={toUserId}
onChange={(e) => {
setToUserId(parseInt(e.target.value, 10))
}}
/>
<DialogFooter className="justify-end">
<DialogClose asChild>
<Button type="button" className="my-2" variant="secondary">
{t("Cancel")}
</Button>
</DialogClose>
<Button disabled={!toUserId || toUserId == 0} type="submit" className="my-2" onClick={onSubmit}>
{t("Move")}
</Button>
</DialogFooter>
</div>
</div>
</ScrollArea>
</DialogContent>
</Dialog>
)
}

View File

@@ -16,6 +16,7 @@ import {
Terminal,
Trash2,
Upload,
UserPen,
} from "lucide-react"
import { forwardRef } from "react"
@@ -37,6 +38,7 @@ export interface IconButtonProps extends ButtonProps {
| "expand"
| "cog"
| "minus"
| "user-pen"
}
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
@@ -97,6 +99,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
case "minus": {
return <Minus />
}
case "user-pen": {
return <UserPen />
}
}
})()}
</Button>

View File

@@ -1,6 +1,7 @@
import { swrFetcher } from "@/api/api"
import { deleteServer, forceUpdateServer } from "@/api/server"
import { ActionButtonGroup } from "@/components/action-button-group"
import { BatchMoveServerIcon } from "@/components/batch-move-server-icon"
import { CopyButton } from "@/components/copy-button"
import { HeaderButtonGroup } from "@/components/header-button-group"
import { InstallCommandsMenu } from "@/components/install-commands"
@@ -213,6 +214,7 @@ export default function ServerPage() {
})
}}
/>
<BatchMoveServerIcon serverIds={selectedRows.map((r) => r.original.id)} />
<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"

View File

@@ -74,6 +74,11 @@ export default function UserPage() {
return row.role === 1 ? t("User") : t("Admin")
},
},
{
header: t("LastLogin"),
accessorKey: "updated_at",
accessorFn: (row) => row.updated_at ? new Date(row.updated_at).toLocaleString() : t("Never"),
},
{
id: "actions",
header: t("Actions"),

View File

@@ -1,14 +1,14 @@
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
/* eslint-disable */
/* tslint:disable */
/*
* ---------------------------------------------------------------
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##
* ## ##
* ## AUTHOR: acacode ##
* ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
* ---------------------------------------------------------------
*/
export interface GithubComNezhahqNezhaModelCommonResponseAny {
data: any
error: string
@@ -188,6 +188,11 @@ export interface ModelAlertRuleForm {
trigger_mode: number
}
export interface ModelBatchMoveServerForm {
ids: number[]
to_user: number
}
export interface ModelCreateFMResponse {
session_id: string
}
@@ -631,6 +636,8 @@ export interface ModelServiceResponseItem {
export interface ModelSetting {
admin_template: string
/** Agent真实IP */
agent_real_ip_header: string
/** 覆盖范围0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器; */
cover: number
custom_code: string
@@ -648,17 +655,17 @@ export interface ModelSetting {
/** 系统语言,默认 zh_CN */
language: string
oauth2_providers: string[]
/** 前端真实IP */
web_real_ip_header: string
/** Agent真实IP */
agent_real_ip_header: string
site_name: string
/** 用于前端判断生成的安装命令是否启用 TLS */
tls: boolean
user_template: string
/** 前端真实IP */
web_real_ip_header: string
}
export interface ModelSettingForm {
/** Agent真实IP */
agent_real_ip_header?: string
cover: number
custom_code?: string
custom_code_dashboard?: string
@@ -671,14 +678,12 @@ export interface ModelSettingForm {
ip_change_notification_group_id: number
/** @minLength 2 */
language: string
/** 前端真实IP */
web_real_ip_header?: string
/** Agent真实IP */
agent_real_ip_header?: string
/** @minLength 1 */
site_name: string
tls?: boolean
user_template?: string
/** 前端真实IP */
web_real_ip_header?: string
}
export interface ModelSettingResponse {