diff --git a/src/api/server.ts b/src/api/server.ts index fe35fc6..955322d 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -1,4 +1,5 @@ import { + ModelBatchMoveServerForm, ModelServer, ModelServerConfigForm, ModelServerForm, @@ -15,6 +16,10 @@ export const deleteServer = async (id: number[]): Promise => { return fetcher(FetcherMethod.POST, "/api/v1/batch-delete/server", id) } +export const batchMoveServer = async (data: ModelBatchMoveServerForm): Promise => { + return fetcher(FetcherMethod.POST, "/api/v1/batch-move/server", data) +} + export const forceUpdateServer = async (id: number[]): Promise => { return fetcher(FetcherMethod.POST, "/api/v1/force-update/server", id) } diff --git a/src/components/batch-move-server-icon.tsx b/src/components/batch-move-server-icon.tsx new file mode 100644 index 0000000..e71b314 --- /dev/null +++ b/src/components/batch-move-server-icon.tsx @@ -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 = ({ serverIds, ...props }) => { + const { t } = useTranslation() + const [open, setOpen] = useState(false) + const [toUserId, setToUserId] = useState(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 ? ( + { + toast(t("Error"), { + description: t("Results.NoRowsAreSelected"), + }) + }} + /> + ) : ( + + + + + + +
+ + {t("BatchMoveServer")} + + +
+ + + + { + setToUserId(parseInt(e.target.value, 10)) + }} + /> + + + + + + +
+
+
+
+
+ ) +} diff --git a/src/components/xui/icon-button.tsx b/src/components/xui/icon-button.tsx index 49a1152..a00163d 100644 --- a/src/components/xui/icon-button.tsx +++ b/src/components/xui/icon-button.tsx @@ -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((props, ref) => { @@ -97,6 +99,9 @@ export const IconButton = forwardRef((props, case "minus": { return } + case "user-pen": { + return + } } })()} diff --git a/src/routes/server.tsx b/src/routes/server.tsx index 1ac27f6..d84e37f 100644 --- a/src/routes/server.tsx +++ b/src/routes/server.tsx @@ -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() { }) }} /> + r.original.id)} /> 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" diff --git a/src/routes/user.tsx b/src/routes/user.tsx index 39a501e..12416a0 100644 --- a/src/routes/user.tsx +++ b/src/routes/user.tsx @@ -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"), diff --git a/src/types/api.ts b/src/types/api.ts index 14cc7cc..eb2cb11 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -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 {