merge: 同步 upstream/main 新功能到定制版本

- feat: Add slider verification code (bd67997)
- fix bug: Inventory cannot be zero (1f7a6ee)
- fix: resolve merge conflicts and lint errors
This commit is contained in:
2026-03-23 21:50:10 -07:00
parent 3806264343
commit d6616c5859
63 changed files with 3111 additions and 1130 deletions
+28 -12
View File
@@ -1,5 +1,6 @@
"use client";
import { useQuery } from "@tanstack/react-query";
import { Badge } from "@workspace/ui/components/badge";
import { Button } from "@workspace/ui/components/button";
import { Switch } from "@workspace/ui/components/switch";
@@ -8,6 +9,10 @@ import {
ProTable,
type ProTableActions,
} from "@workspace/ui/composed/pro-table/pro-table";
import {
getGroupConfig,
getNodeGroupList,
} from "@workspace/ui/services/admin/group";
import {
createNode,
deleteNode,
@@ -16,8 +21,6 @@ import {
toggleNodeStatus,
updateNode,
} from "@workspace/ui/services/admin/server";
import { getGroupConfig, getNodeGroupList } from "@workspace/ui/services/admin/group";
import { useQuery } from "@tanstack/react-query";
import { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { toast } from "sonner";
@@ -52,7 +55,7 @@ export default function Nodes() {
},
});
const isGroupEnabled = groupConfigData?.enabled || false;
const isGroupEnabled = groupConfigData?.enabled;
// Dynamic columns based on group feature status
const columns = useMemo(() => {
@@ -121,13 +124,13 @@ export default function Nodes() {
id: "node_group_ids",
header: t("nodeGroups", "Node Groups"),
cell: ({ row }: { row: any }) => {
const groupIds = row.original.node_group_ids as number[] || [];
const groupIds = (row.original.node_group_ids as number[]) || [];
// Public node indicator (when node_group_ids is empty)
if (groupIds.length === 0) {
return (
<div className="flex items-center gap-2">
<Badge variant="secondary" className="text-xs">
<Badge className="text-xs" variant="secondary">
{t("public", "Public")}
</Badge>
</div>
@@ -151,7 +154,14 @@ export default function Nodes() {
}
return baseColumns;
}, [isGroupEnabled, nodeGroupsData, t, getServerName, getServerAddress, getProtocolPort]);
}, [
isGroupEnabled,
nodeGroupsData,
t,
getServerName,
getServerAddress,
getProtocolPort,
]);
return (
<ProTable<API.Node, { search: string; node_group_id?: number }>
@@ -168,7 +178,10 @@ export default function Nodes() {
const body: API.UpdateNodeRequest = {
...row,
...values,
node_group_ids: values.node_group_ids?.map((id: string | number) => Number(id)) || [],
node_group_ids:
values.node_group_ids?.map((id: string | number) =>
Number(id)
) || [],
} as any;
await updateNode(body);
toast.success(t("updated", "Updated"));
@@ -274,9 +287,10 @@ export default function Nodes() {
port: Number(values.port!),
tags: values.tags || [],
};
// Add node_group_ids if it exists
if (values.node_group_ids) {
body.node_group_ids = values.node_group_ids.map((id: string | number) => Number(id));
body.node_group_ids = values.node_group_ids.map(
(id: string | number) => Number(id)
);
}
await createNode(body);
toast.success(t("created", "Created"));
@@ -290,8 +304,8 @@ export default function Nodes() {
return false;
}
}}
title={t("drawerCreateTitle", "Create Node")}
trigger={t("create", "Create")}
title={t("drawerCreateTitle", "Create Landing Node")}
trigger={t("create", "Create Landing Node")}
/>
),
}}
@@ -372,7 +386,9 @@ export default function Nodes() {
page: pagination.page,
size: pagination.size,
search: filter?.search || undefined,
node_group_id: filter?.node_group_id ? Number(filter.node_group_id) : undefined,
node_group_id: filter?.node_group_id
? Number(filter.node_group_id)
: undefined,
};
const { data } = await filterNodeList(filters);
+30 -12
View File
@@ -1,6 +1,7 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
import { Button } from "@workspace/ui/components/button";
import { Checkbox } from "@workspace/ui/components/checkbox";
import {
@@ -25,8 +26,10 @@ import {
import { Combobox } from "@workspace/ui/composed/combobox";
import { EnhancedInput } from "@workspace/ui/composed/enhanced-input";
import TagInput from "@workspace/ui/composed/tag-input";
import { getGroupConfig, getNodeGroupList } from "@workspace/ui/services/admin/group";
import { useQuery } from "@tanstack/react-query";
import {
getGroupConfig,
getNodeGroupList,
} from "@workspace/ui/services/admin/group";
import type { TFunction } from "i18next";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
@@ -150,7 +153,7 @@ export default function NodeForm(props: {
},
});
const isGroupEnabled = groupConfigData?.enabled || false;
const isGroupEnabled = groupConfigData?.enabled;
useEffect(() => {
if (initialValues) {
@@ -166,15 +169,21 @@ export default function NodeForm(props: {
// Copy only the values we need from initialValues
if (initialValues.name) resetValues.name = initialValues.name;
if (initialValues.server_id) resetValues.server_id = initialValues.server_id;
if (initialValues.server_id)
resetValues.server_id = initialValues.server_id;
if (initialValues.protocol) resetValues.protocol = initialValues.protocol;
if (initialValues.address) resetValues.address = initialValues.address;
if (initialValues.port) resetValues.port = initialValues.port;
if (initialValues.tags) resetValues.tags = initialValues.tags;
// Convert node_group_ids from number[] to string[], ensure it's always an array
if (initialValues.node_group_ids && Array.isArray(initialValues.node_group_ids)) {
resetValues.node_group_ids = initialValues.node_group_ids.map((id: string | number) => String(id));
if (
initialValues.node_group_ids &&
Array.isArray(initialValues.node_group_ids)
) {
resetValues.node_group_ids = initialValues.node_group_ids.map(
(id: string | number) => String(id)
);
} else {
resetValues.node_group_ids = [];
}
@@ -449,23 +458,32 @@ export default function NodeForm(props: {
<div className="grid grid-cols-2 gap-2">
{nodeGroupsData?.map((g) => (
<div
key={g.id}
className="flex items-center space-x-2"
key={g.id}
>
<Checkbox
checked={field.value?.includes(String(g.id))}
id={`node-group-${g.id}`}
checked={field.value?.includes(String(g.id)) || false}
onCheckedChange={(checked) => {
// Ensure field.value is always an array
const currentValue = Array.isArray(field.value) ? field.value : [];
const currentValue = Array.isArray(
field.value
)
? field.value
: [];
if (checked) {
const newValue = [...currentValue, String(g.id)];
const newValue = [
...currentValue,
String(g.id),
];
form.setValue(field.name, newValue, {
shouldValidate: true,
shouldDirty: true,
});
} else {
const newValue = currentValue.filter((v: string) => v !== String(g.id));
const newValue = currentValue.filter(
(v: string) => v !== String(g.id)
);
form.setValue(field.name, newValue, {
shouldValidate: true,
shouldDirty: true,
@@ -474,8 +492,8 @@ export default function NodeForm(props: {
}}
/>
<Label
htmlFor={`node-group-${g.id}`}
className="cursor-pointer"
htmlFor={`node-group-${g.id}`}
>
{g.name}
</Label>