mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-04 12:40:08 +00:00
feat: 批量转移服务器给其他用户
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ModelBatchMoveServerForm,
|
||||||
ModelServer,
|
ModelServer,
|
||||||
ModelServerConfigForm,
|
ModelServerConfigForm,
|
||||||
ModelServerForm,
|
ModelServerForm,
|
||||||
@@ -15,6 +16,10 @@ 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 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> => {
|
export const forceUpdateServer = async (id: number[]): Promise<ModelServerTaskResponse> => {
|
||||||
return fetcher<ModelServerTaskResponse>(FetcherMethod.POST, "/api/v1/force-update/server", id)
|
return fetcher<ModelServerTaskResponse>(FetcherMethod.POST, "/api/v1/force-update/server", id)
|
||||||
}
|
}
|
||||||
|
|||||||
100
src/components/batch-move-server-icon.tsx
Normal file
100
src/components/batch-move-server-icon.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
Terminal,
|
Terminal,
|
||||||
Trash2,
|
Trash2,
|
||||||
Upload,
|
Upload,
|
||||||
|
UserPen,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { forwardRef } from "react"
|
import { forwardRef } from "react"
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ export interface IconButtonProps extends ButtonProps {
|
|||||||
| "expand"
|
| "expand"
|
||||||
| "cog"
|
| "cog"
|
||||||
| "minus"
|
| "minus"
|
||||||
|
| "user-pen"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
||||||
@@ -97,6 +99,9 @@ export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props,
|
|||||||
case "minus": {
|
case "minus": {
|
||||||
return <Minus />
|
return <Minus />
|
||||||
}
|
}
|
||||||
|
case "user-pen": {
|
||||||
|
return <UserPen />
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { swrFetcher } from "@/api/api"
|
import { swrFetcher } from "@/api/api"
|
||||||
import { deleteServer, forceUpdateServer } from "@/api/server"
|
import { deleteServer, forceUpdateServer } from "@/api/server"
|
||||||
import { ActionButtonGroup } from "@/components/action-button-group"
|
import { ActionButtonGroup } from "@/components/action-button-group"
|
||||||
|
import { BatchMoveServerIcon } from "@/components/batch-move-server-icon"
|
||||||
import { CopyButton } from "@/components/copy-button"
|
import { CopyButton } from "@/components/copy-button"
|
||||||
import { HeaderButtonGroup } from "@/components/header-button-group"
|
import { HeaderButtonGroup } from "@/components/header-button-group"
|
||||||
import { InstallCommandsMenu } from "@/components/install-commands"
|
import { InstallCommandsMenu } from "@/components/install-commands"
|
||||||
@@ -213,6 +214,7 @@ export default function ServerPage() {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<BatchMoveServerIcon serverIds={selectedRows.map((r) => r.original.id)} />
|
||||||
<ServerConfigCardBatch
|
<ServerConfigCardBatch
|
||||||
sid={selectedRows.map((r) => r.original.id)}
|
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"
|
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"
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ export default function UserPage() {
|
|||||||
return row.role === 1 ? t("User") : t("Admin")
|
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",
|
id: "actions",
|
||||||
header: t("Actions"),
|
header: t("Actions"),
|
||||||
|
|||||||
@@ -188,6 +188,11 @@ export interface ModelAlertRuleForm {
|
|||||||
trigger_mode: number
|
trigger_mode: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelBatchMoveServerForm {
|
||||||
|
ids: number[]
|
||||||
|
to_user: number
|
||||||
|
}
|
||||||
|
|
||||||
export interface ModelCreateFMResponse {
|
export interface ModelCreateFMResponse {
|
||||||
session_id: string
|
session_id: string
|
||||||
}
|
}
|
||||||
@@ -631,6 +636,8 @@ export interface ModelServiceResponseItem {
|
|||||||
|
|
||||||
export interface ModelSetting {
|
export interface ModelSetting {
|
||||||
admin_template: string
|
admin_template: string
|
||||||
|
/** Agent真实IP */
|
||||||
|
agent_real_ip_header: string
|
||||||
/** 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;) */
|
/** 覆盖范围(0:提醒未被 IgnoredIPNotification 包含的所有服务器; 1:仅提醒被 IgnoredIPNotification 包含的服务器;) */
|
||||||
cover: number
|
cover: number
|
||||||
custom_code: string
|
custom_code: string
|
||||||
@@ -648,17 +655,17 @@ export interface ModelSetting {
|
|||||||
/** 系统语言,默认 zh_CN */
|
/** 系统语言,默认 zh_CN */
|
||||||
language: string
|
language: string
|
||||||
oauth2_providers: string[]
|
oauth2_providers: string[]
|
||||||
/** 前端真实IP */
|
|
||||||
web_real_ip_header: string
|
|
||||||
/** Agent真实IP */
|
|
||||||
agent_real_ip_header: string
|
|
||||||
site_name: string
|
site_name: string
|
||||||
/** 用于前端判断生成的安装命令是否启用 TLS */
|
/** 用于前端判断生成的安装命令是否启用 TLS */
|
||||||
tls: boolean
|
tls: boolean
|
||||||
user_template: string
|
user_template: string
|
||||||
|
/** 前端真实IP */
|
||||||
|
web_real_ip_header: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelSettingForm {
|
export interface ModelSettingForm {
|
||||||
|
/** Agent真实IP */
|
||||||
|
agent_real_ip_header?: string
|
||||||
cover: number
|
cover: number
|
||||||
custom_code?: string
|
custom_code?: string
|
||||||
custom_code_dashboard?: string
|
custom_code_dashboard?: string
|
||||||
@@ -671,14 +678,12 @@ export interface ModelSettingForm {
|
|||||||
ip_change_notification_group_id: number
|
ip_change_notification_group_id: number
|
||||||
/** @minLength 2 */
|
/** @minLength 2 */
|
||||||
language: string
|
language: string
|
||||||
/** 前端真实IP */
|
|
||||||
web_real_ip_header?: string
|
|
||||||
/** Agent真实IP */
|
|
||||||
agent_real_ip_header?: string
|
|
||||||
/** @minLength 1 */
|
/** @minLength 1 */
|
||||||
site_name: string
|
site_name: string
|
||||||
tls?: boolean
|
tls?: boolean
|
||||||
user_template?: string
|
user_template?: string
|
||||||
|
/** 前端真实IP */
|
||||||
|
web_real_ip_header?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelSettingResponse {
|
export interface ModelSettingResponse {
|
||||||
|
|||||||
Reference in New Issue
Block a user