Skip to content

Commit 3ef5ec8

Browse files
committed
feat: 导入主控时支持拖拽修改导入的顺序;
update: 更新负载均衡placeHolder文案
1 parent 86a791e commit 3ef5ec8

3 files changed

Lines changed: 171 additions & 73 deletions

File tree

web/src/components/services/service-create-modal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ export default function ScenarioCreateModal({
790790
</Tooltip>
791791
</div>
792792
}
793-
placeholder="192.168.1.1&#10;192.168.1.2"
793+
placeholder="192.168.1.1:80&#10;192.168.1.2:80"
794794
minRows={2}
795795
value={formData.extendTargetAddresses}
796796
onValueChange={(v) => handleField("extendTargetAddresses", v)}
@@ -979,7 +979,7 @@ export default function ScenarioCreateModal({
979979
</Tooltip>
980980
</div>
981981
}
982-
placeholder="192.168.1.1&#10;192.168.1.2"
982+
placeholder="192.168.1.1:80&#10;192.168.1.2:80"
983983
minRows={2}
984984
value={formData.extendTargetAddressesSingle}
985985
onValueChange={(v) => handleField("extendTargetAddressesSingle", v)}
@@ -1244,7 +1244,7 @@ export default function ScenarioCreateModal({
12441244
</Tooltip>
12451245
</div>
12461246
}
1247-
placeholder="192.168.1.1&#10;192.168.1.2"
1247+
placeholder="192.168.1.1:80&#10;192.168.1.2:80"
12481248
minRows={2}
12491249
value={formData.extendTargetAddresses2}
12501250
onValueChange={(v) => handleField("extendTargetAddresses2", v)}

web/src/components/tunnels/simple-create-tunnel-modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ export default function SimpleCreateTunnelModal({
10281028
</Tooltip>
10291029
</div>
10301030
}
1031-
placeholder="192.168.1.1&#10;192.168.1.2"
1031+
placeholder="192.168.1.1:80&#10;192.168.1.2:80"
10321032
minRows={2}
10331033
value={formData.extendTargetAddresses}
10341034
onValueChange={(v) => handleField("extendTargetAddresses", v)}

web/src/pages/endpoints/index.tsx

Lines changed: 167 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,23 @@ import { useState, useEffect, useRef, useCallback } from "react";
3131
import { useNavigate } from "react-router-dom";
3232
import { Icon } from "@iconify/react";
3333
import { addToast } from "@heroui/toast";
34+
import {
35+
DndContext,
36+
closestCenter,
37+
KeyboardSensor,
38+
PointerSensor,
39+
useSensor,
40+
useSensors,
41+
DragEndEvent,
42+
} from "@dnd-kit/core";
43+
import {
44+
arrayMove,
45+
SortableContext,
46+
sortableKeyboardCoordinates,
47+
useSortable,
48+
verticalListSortingStrategy,
49+
} from "@dnd-kit/sortable";
50+
import { CSS } from "@dnd-kit/utilities";
3451
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3552
import {
3653
faPlus,
@@ -102,6 +119,86 @@ interface EndpointFormData {
102119
apiKey: string;
103120
}
104121

122+
// 可排序的表格行组件
123+
function SortableTableRow({
124+
id,
125+
result,
126+
index,
127+
}: {
128+
id: string;
129+
result: any;
130+
index: number;
131+
}) {
132+
const {
133+
attributes,
134+
listeners,
135+
setNodeRef,
136+
transform,
137+
transition,
138+
isDragging,
139+
} = useSortable({ id });
140+
141+
const style = {
142+
transform: CSS.Transform.toString(transform),
143+
transition,
144+
opacity: isDragging ? 0.5 : 1,
145+
};
146+
147+
return (
148+
<tr
149+
ref={setNodeRef}
150+
style={style}
151+
className="border-b border-divider hover:bg-default-50"
152+
>
153+
<td className="px-3 py-3">
154+
<div className="flex items-center gap-2">
155+
<button
156+
{...attributes}
157+
{...listeners}
158+
className="cursor-grab active:cursor-grabbing text-default-400 hover:text-default-600"
159+
>
160+
<FontAwesomeIcon icon={faGrip} />
161+
</button>
162+
<span className="text-small">{result.name}</span>
163+
</div>
164+
</td>
165+
<td className="px-3 py-3 text-small font-mono text-xs">
166+
{result.url}
167+
{result.apiPath}
168+
</td>
169+
<td className="px-3 py-3 text-small">
170+
<span
171+
className={`font-mono ${
172+
result.status === "success"
173+
? "text-success"
174+
: result.status === "low_version"
175+
? "text-warning"
176+
: "text-danger"
177+
}`}
178+
>
179+
{result.version}
180+
</span>
181+
</td>
182+
<td className="px-3 py-3">
183+
<div className="flex flex-col gap-1">
184+
<span
185+
className={`text-xs ${
186+
result.status === "success"
187+
? "text-success"
188+
: result.status === "low_version"
189+
? "text-warning"
190+
: "text-danger"
191+
}`}
192+
>
193+
{result.canImport ? "✓ 可导入" : "✗ 不可导入"}
194+
</span>
195+
<span className="text-xs text-default-400">{result.message}</span>
196+
</div>
197+
</td>
198+
</tr>
199+
);
200+
}
201+
105202
export default function EndpointsPage() {
106203
const [isSubmitting, setIsSubmitting] = useState(false);
107204
const [selectedFile, setSelectedFile] = useState<File | null>(null);
@@ -138,8 +235,17 @@ export default function EndpointsPage() {
138235
} = useDisclosure();
139236

140237
const [importValidateResults, setImportValidateResults] = useState<any[]>([]);
238+
const [sortedValidateResults, setSortedValidateResults] = useState<any[]>([]);
141239
const [importFileData, setImportFileData] = useState<any>(null);
142240

241+
// 拖拽传感器配置
242+
const sensors = useSensors(
243+
useSensor(PointerSensor),
244+
useSensor(KeyboardSensor, {
245+
coordinateGetter: sortableKeyboardCoordinates,
246+
})
247+
);
248+
143249
const {
144250
isOpen: isAddOpen,
145251
onOpen: onAddOpen,
@@ -559,7 +665,9 @@ export default function EndpointsPage() {
559665
}
560666

561667
// 关闭导入窗口,显示验证结果窗口
562-
setImportValidateResults(validateResult.results || []);
668+
const results = validateResult.results || [];
669+
setImportValidateResults(results);
670+
setSortedValidateResults(results); // 初始化排序结果
563671
onImportOpenChange();
564672
onImportValidateOpen();
565673
} catch (error) {
@@ -575,10 +683,24 @@ export default function EndpointsPage() {
575683
}
576684
};
577685

686+
// 处理拖拽结束事件
687+
const handleDragEnd = (event: DragEndEvent) => {
688+
const { active, over } = event;
689+
690+
if (over && active.id !== over.id) {
691+
setSortedValidateResults((items) => {
692+
const oldIndex = items.findIndex((item, idx) => `item-${idx}` === active.id);
693+
const newIndex = items.findIndex((item, idx) => `item-${idx}` === over.id);
694+
695+
return arrayMove(items, oldIndex, newIndex);
696+
});
697+
}
698+
};
699+
578700
// 确认导入 - 只导入可导入的主控
579701
const handleConfirmImport = async () => {
580-
// 筛选出可导入的主控
581-
const importableEndpoints = importValidateResults
702+
// 筛选出可导入的主控,使用排序后的结果
703+
const importableEndpoints = sortedValidateResults
582704
.filter((result) => result.canImport)
583705
.map((result) => ({
584706
name: result.name,
@@ -621,6 +743,7 @@ export default function EndpointsPage() {
621743
setSelectedFile(null);
622744
setImportFileData(null);
623745
setImportValidateResults([]);
746+
setSortedValidateResults([]);
624747
if (fileInputRef.current) {
625748
fileInputRef.current.value = "";
626749
}
@@ -1885,78 +2008,52 @@ export default function EndpointsPage() {
18852008
<ModalBody>
18862009
<div className="flex flex-col gap-4">
18872010
<p className="text-small text-default-500">
1888-
共检测到 {importValidateResults.length} 个主控,请查看验证结果:
2011+
共检测到 {importValidateResults.length} 个主控,请查看验证结果(可拖动排序)
18892012
</p>
18902013
<div className="max-h-[400px] overflow-y-auto">
1891-
<table className="w-full">
1892-
<thead className="sticky top-0 bg-default-100 z-10">
1893-
<tr>
1894-
<th className="text-left px-3 py-2 text-small font-semibold">
1895-
名称
1896-
</th>
1897-
<th className="text-left px-3 py-2 text-small font-semibold">
1898-
URL
1899-
</th>
1900-
<th className="text-left px-3 py-2 text-small font-semibold">
1901-
版本
1902-
</th>
1903-
<th className="text-left px-3 py-2 text-small font-semibold">
1904-
状态
1905-
</th>
1906-
</tr>
1907-
</thead>
1908-
<tbody>
1909-
{importValidateResults.map((result, index) => (
1910-
<tr
1911-
key={index}
1912-
className="border-b border-divider hover:bg-default-50"
1913-
>
1914-
<td className="px-3 py-3 text-small">
1915-
{result.name}
1916-
</td>
1917-
<td className="px-3 py-3 text-small font-mono text-xs">
1918-
{result.url}
1919-
{result.apiPath}
1920-
</td>
1921-
<td className="px-3 py-3 text-small">
1922-
<span
1923-
className={`font-mono ${
1924-
result.status === "success"
1925-
? "text-success"
1926-
: result.status === "low_version"
1927-
? "text-warning"
1928-
: "text-danger"
1929-
}`}
1930-
>
1931-
{result.version}
1932-
</span>
1933-
</td>
1934-
<td className="px-3 py-3">
1935-
<div className="flex flex-col gap-1">
1936-
<span
1937-
className={`text-xs ${
1938-
result.status === "success"
1939-
? "text-success"
1940-
: result.status === "low_version"
1941-
? "text-warning"
1942-
: "text-danger"
1943-
}`}
1944-
>
1945-
{result.canImport ? "✓ 可导入" : "✗ 不可导入"}
1946-
</span>
1947-
<span className="text-xs text-default-400">
1948-
{result.message}
1949-
</span>
1950-
</div>
1951-
</td>
2014+
<DndContext
2015+
sensors={sensors}
2016+
collisionDetection={closestCenter}
2017+
onDragEnd={handleDragEnd}
2018+
>
2019+
<table className="w-full">
2020+
<thead className="sticky top-0 bg-default-100 z-10">
2021+
<tr>
2022+
<th className="text-left px-3 py-2 text-small font-semibold">
2023+
名称
2024+
</th>
2025+
<th className="text-left px-3 py-2 text-small font-semibold">
2026+
URL
2027+
</th>
2028+
<th className="text-left px-3 py-2 text-small font-semibold">
2029+
版本
2030+
</th>
2031+
<th className="text-left px-3 py-2 text-small font-semibold">
2032+
状态
2033+
</th>
19522034
</tr>
1953-
))}
1954-
</tbody>
1955-
</table>
2035+
</thead>
2036+
<tbody>
2037+
<SortableContext
2038+
items={sortedValidateResults.map((_, idx) => `item-${idx}`)}
2039+
strategy={verticalListSortingStrategy}
2040+
>
2041+
{sortedValidateResults.map((result, index) => (
2042+
<SortableTableRow
2043+
key={`item-${index}`}
2044+
id={`item-${index}`}
2045+
result={result}
2046+
index={index}
2047+
/>
2048+
))}
2049+
</SortableContext>
2050+
</tbody>
2051+
</table>
2052+
</DndContext>
19562053
</div>
19572054
<div className="rounded-lg bg-default-100 p-3">
19582055
<p className="text-xs text-default-600">
1959-
注意:只有版本 ≥ 1.10.0 的主控才会被导入,低版本或连接失败的主控将被自动跳过。
2056+
注意:只有版本 ≥ 1.10.0 的主控才会被导入,低版本或连接失败的主控将被自动跳过。拖动行可调整导入顺序。
19602057
</p>
19612058
</div>
19622059
</div>
@@ -1969,6 +2066,7 @@ export default function EndpointsPage() {
19692066
onPress={() => {
19702067
onClose();
19712068
setImportValidateResults([]);
2069+
setSortedValidateResults([]);
19722070
setImportFileData(null);
19732071
}}
19742072
>

0 commit comments

Comments
 (0)