@aixnn/aixautots 0.0.0-stage → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +239 -2
  2. package/dist/ScriptStudio-DaCqOF5H.d.ts +43 -0
  3. package/dist/agent-runner-V60B0is1.d.ts +57 -0
  4. package/dist/automation-queue-8HqRJV_K.d.ts +72 -0
  5. package/dist/chunk-536WVCTB.js +5744 -0
  6. package/dist/chunk-5BMFMDQR.js +18 -0
  7. package/dist/chunk-64Q7BBIQ.js +1347 -0
  8. package/dist/chunk-6YUAACW6.js +866 -0
  9. package/dist/chunk-AIUOLJZA.js +412 -0
  10. package/dist/chunk-CVV7FOMQ.js +175 -0
  11. package/dist/chunk-EHF2WQUR.js +2042 -0
  12. package/dist/chunk-EVHTAXQG.js +273 -0
  13. package/dist/chunk-FW23FLAF.js +56 -0
  14. package/dist/chunk-GKEPMDXO.js +410 -0
  15. package/dist/chunk-H7MHGYF3.js +341 -0
  16. package/dist/chunk-MRQ7VJFJ.js +1074 -0
  17. package/dist/chunk-NWFYTFMI.js +65 -0
  18. package/dist/chunk-OEZFYKL6.js +1 -0
  19. package/dist/chunk-PCYERBNZ.js +1564 -0
  20. package/dist/chunk-PUPVZRKZ.js +668 -0
  21. package/dist/chunk-SUKFJ73W.js +217 -0
  22. package/dist/config-Bm1hmXhD.d.ts +1380 -0
  23. package/dist/coords-LPR_MPyp.d.ts +22 -0
  24. package/dist/core/index.d.ts +135 -0
  25. package/dist/core/index.js +5 -0
  26. package/dist/device-normalize-BaThgnH7.d.ts +43 -0
  27. package/dist/device-session-KZVGLPOI.js +2 -0
  28. package/dist/i18n/index.d.ts +30 -0
  29. package/dist/i18n/index.js +1 -0
  30. package/dist/mirror-session-C_1vFkRV.d.ts +108 -0
  31. package/dist/nav/index.d.ts +19 -0
  32. package/dist/nav/index.js +1 -0
  33. package/dist/react/index.d.ts +487 -0
  34. package/dist/react/index.js +7 -0
  35. package/dist/script-debugger-Cw5jVRoz.d.ts +114 -0
  36. package/dist/task-agent-runner-eD9-SCJz.d.ts +160 -0
  37. package/dist/types-C2WXVcam.d.ts +1005 -0
  38. package/dist/ui/billing.d.ts +48 -0
  39. package/dist/ui/billing.js +425 -0
  40. package/dist/ui/device-create.d.ts +55 -0
  41. package/dist/ui/device-create.js +9 -0
  42. package/dist/ui/device-mirror.d.ts +42 -0
  43. package/dist/ui/device-mirror.js +10 -0
  44. package/dist/ui/devices.d.ts +223 -0
  45. package/dist/ui/devices.js +15 -0
  46. package/dist/ui/editor.d.ts +64 -0
  47. package/dist/ui/editor.js +344 -0
  48. package/dist/ui/index.d.ts +104 -0
  49. package/dist/ui/index.js +676 -0
  50. package/dist/ui/inspector.d.ts +5 -0
  51. package/dist/ui/inspector.js +1250 -0
  52. package/dist/ui/market.d.ts +10 -0
  53. package/dist/ui/market.js +60 -0
  54. package/dist/ui/mirror.d.ts +52 -0
  55. package/dist/ui/mirror.js +3 -0
  56. package/dist/ui/primitives/index.d.ts +101 -0
  57. package/dist/ui/primitives/index.js +1 -0
  58. package/dist/ui/script-studio.d.ts +85 -0
  59. package/dist/ui/script-studio.js +10 -0
  60. package/dist/ui/scripts.d.ts +18 -0
  61. package/dist/ui/scripts.js +210 -0
  62. package/dist/ui/tasks.d.ts +10 -0
  63. package/dist/ui/tasks.js +1039 -0
  64. package/dist/use-script-run-Diay7ibY.d.ts +50 -0
  65. package/package.json +76 -4
  66. package/styles.css +97 -0
  67. package/tailwind-preset.cjs +84 -0
@@ -0,0 +1,1074 @@
1
+ import { cn, Select, Dialog, Button, Input, Badge } from './chunk-EVHTAXQG.js';
2
+ import { useNodes, useDevices } from './chunk-EHF2WQUR.js';
3
+ import { useClient } from './chunk-SUKFJ73W.js';
4
+ import { useMemo, useEffect, useState, useCallback } from 'react';
5
+ import { Server, RefreshCw, Smartphone, Usb, Wifi, Info } from 'lucide-react';
6
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+
8
+ var SCOPE_META = {
9
+ platform: { label: "\u5E73\u53F0", order: 0 },
10
+ project: { label: "\u9879\u76EE", order: 1 },
11
+ personal: { label: "\u4E2A\u4EBA", order: 2 }
12
+ };
13
+ var CURRENCY_SYMBOL = { CNY: "\xA5", USD: "$" };
14
+ function nodeMetric(n) {
15
+ if (n.is_platform) {
16
+ if (n.starting_price == null) return "\u5E73\u53F0\u8282\u70B9";
17
+ const sym = CURRENCY_SYMBOL[n.price_currency ?? "CNY"] ?? "";
18
+ return `${sym}${Number(n.starting_price)}/\u5C0F\u65F6 \u8D77`;
19
+ }
20
+ return `${n.device_count} devices`;
21
+ }
22
+ function NodePicker({ value, onChange, hidePlatform = false }) {
23
+ const { data, loading, error, refetch } = useNodes();
24
+ const nodes = useMemo(() => data ?? [], [data]);
25
+ useEffect(() => {
26
+ void refetch();
27
+ }, [refetch]);
28
+ const onlineNodes = useMemo(
29
+ () => nodes.filter(
30
+ (n) => n.status === "online" && !(hidePlatform && (n.is_platform || n.scope === "platform"))
31
+ ),
32
+ [nodes, hidePlatform]
33
+ );
34
+ useEffect(() => {
35
+ if (value && onlineNodes.length > 0 && !onlineNodes.some((n) => n.id === value)) {
36
+ onChange("");
37
+ }
38
+ }, [value, onlineNodes, onChange]);
39
+ const groups = useMemo(() => {
40
+ const by = {
41
+ platform: [],
42
+ project: [],
43
+ personal: []
44
+ };
45
+ for (const n of onlineNodes) (by[n.scope] ?? by.personal).push(n);
46
+ return Object.keys(by).filter((s) => by[s].length > 0).sort((a, b) => SCOPE_META[a].order - SCOPE_META[b].order).map((scope) => ({ scope, rows: by[scope] }));
47
+ }, [onlineNodes]);
48
+ if (loading && nodes.length === 0) {
49
+ return /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-body-sm text-muted-foreground", children: "\u52A0\u8F7D\u8282\u70B9\u4E2D\u2026" });
50
+ }
51
+ if (error) {
52
+ return /* @__PURE__ */ jsx("p", { className: "py-2 text-[11px] text-destructive", children: error.message });
53
+ }
54
+ if (onlineNodes.length === 0) {
55
+ return /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-body-sm text-muted-foreground", children: "\u6682\u65E0\u5728\u7EBF\u8282\u70B9\u3002\u8BF7\u5148\u542F\u52A8\u4E00\u4E2A\u8282\u70B9\uFF08\u672C\u673A\u8282\u70B9\u6216\u6CE8\u518C\u65B0\u8282\u70B9\uFF09\u540E\u518D\u521B\u5EFA\u8BBE\u5907\u3002" });
56
+ }
57
+ return /* @__PURE__ */ jsx("div", { className: "space-y-3", children: groups.map(({ scope, rows }) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
58
+ /* @__PURE__ */ jsx("p", { className: "text-label-caps text-muted-foreground", children: SCOPE_META[scope].label }),
59
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: rows.map((n) => {
60
+ const online = n.status === "online";
61
+ const selected = n.id === value;
62
+ return /* @__PURE__ */ jsxs(
63
+ "button",
64
+ {
65
+ type: "button",
66
+ disabled: !online,
67
+ onClick: () => onChange(n.id),
68
+ className: cn(
69
+ "flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition-colors",
70
+ selected ? "border-primary bg-primary/5" : "border-border bg-card hover:border-primary/60",
71
+ !online && "cursor-not-allowed opacity-50"
72
+ ),
73
+ children: [
74
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-secondary text-muted-foreground", children: /* @__PURE__ */ jsx(Server, { className: "h-4 w-4" }) }),
75
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
76
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-body-sm font-medium text-foreground", children: n.name }),
77
+ /* @__PURE__ */ jsxs("span", { className: "block truncate text-[11px] text-muted-foreground", children: [
78
+ n.region || "\u2014",
79
+ " \xB7 ",
80
+ nodeMetric(n)
81
+ ] })
82
+ ] }),
83
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
84
+ /* @__PURE__ */ jsx(
85
+ "span",
86
+ {
87
+ className: cn(
88
+ "h-1.5 w-1.5 rounded-full",
89
+ online ? "bg-success" : "bg-muted-foreground"
90
+ )
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx(
94
+ "span",
95
+ {
96
+ className: cn(
97
+ "text-[10px] font-bold tracking-wide",
98
+ online ? "text-success" : "text-muted-foreground"
99
+ ),
100
+ children: online ? "ONLINE" : "OFFLINE"
101
+ }
102
+ )
103
+ ] })
104
+ ]
105
+ },
106
+ n.id
107
+ );
108
+ }) })
109
+ ] }, scope)) });
110
+ }
111
+ var PLATFORM_OPTIONS = [
112
+ { value: "android", label: "Android" },
113
+ { value: "ios", label: "iOS" }
114
+ ];
115
+ var TIER_OPTIONS = [
116
+ { value: "2048", label: "\u6807\u51C6 standard \xB7 2 GB" },
117
+ { value: "4096", label: "\u589E\u5F3A plus \xB7 4 GB" },
118
+ { value: "8192", label: "\u4E13\u4E1A pro \xB7 8 GB" },
119
+ { value: "12288", label: "\u65D7\u8230 max \xB7 12 GB" }
120
+ ];
121
+ var REGION_OPTIONS = [
122
+ { value: "auto", label: "\u81EA\u52A8\u5339\u914D IP auto (proxy)" },
123
+ { value: "cn", label: "\u4E2D\u56FD China \xB7 \u79FB\u52A8 CMCC" },
124
+ { value: "hk", label: "\u9999\u6E2F Hong Kong \xB7 CSL" },
125
+ { value: "tw", label: "\u53F0\u6E7E Taiwan \xB7 \u4E2D\u534E\u7535\u4FE1" },
126
+ { value: "us", label: "\u7F8E\u56FD US \xB7 T-Mobile" },
127
+ { value: "gb", label: "\u82F1\u56FD UK \xB7 EE" },
128
+ { value: "de", label: "\u5FB7\u56FD Germany \xB7 Telekom" },
129
+ { value: "sg", label: "\u65B0\u52A0\u5761 Singapore \xB7 Singtel" },
130
+ { value: "jp", label: "\u65E5\u672C Japan \xB7 NTT Docomo" },
131
+ { value: "kr", label: "\u97E9\u56FD Korea \xB7 SKT" }
132
+ ];
133
+ var GPU_OPTIONS = [
134
+ { value: "auto", label: "\u81EA\u52A8 auto(\u6709 GPU \u7528\u786C\u4EF6,\u5426\u5219\u8F6F\u4EF6)" },
135
+ { value: "host", label: "\u786C\u4EF6 GPU host(\u8282\u70B9\u9700 /dev/dri)" },
136
+ { value: "guest", label: "\u8F6F\u4EF6\u6E32\u67D3 guest(\u65E0\u9700 GPU)" }
137
+ ];
138
+ function VirtualSpecStep({ spec, onChange, nodeId }) {
139
+ const client = useClient();
140
+ const platform = spec.platform ?? "android";
141
+ const memory = spec.hardware?.memory_mb ?? 2048;
142
+ const [versions, setVersions] = useState([]);
143
+ const [loading, setLoading] = useState(false);
144
+ const [installed, setInstalled] = useState([]);
145
+ const [installedLoading, setInstalledLoading] = useState(false);
146
+ const [catalog, setCatalog] = useState([]);
147
+ const [brand, setBrand] = useState("");
148
+ const patch = (p) => onChange({ ...spec, ...p });
149
+ const setPlatform = (next) => {
150
+ setBrand("");
151
+ patch({
152
+ platform: next,
153
+ image_id: void 0,
154
+ os_version: void 0,
155
+ android_version: void 0,
156
+ model: void 0,
157
+ // Android 虚拟机:root + Frida 为内置默认(开启且不向用户暴露开关);切到 iOS 清除。
158
+ frida: next === "android" ? true : void 0,
159
+ kernelsu: next === "android" ? true : void 0
160
+ });
161
+ };
162
+ const setImage = (id) => {
163
+ const opt = versions.find((o) => o.id === id);
164
+ const nextAv = platform === "android" ? opt?.version : void 0;
165
+ patch({
166
+ image_id: id || void 0,
167
+ os_version: opt?.version,
168
+ android_version: nextAv
169
+ });
170
+ };
171
+ useEffect(() => {
172
+ let alive = true;
173
+ setLoading(true);
174
+ client.images.options(platform).then((opts) => {
175
+ if (alive) setVersions(opts);
176
+ }).catch(() => {
177
+ if (alive) setVersions([]);
178
+ }).finally(() => {
179
+ if (alive) setLoading(false);
180
+ });
181
+ return () => {
182
+ alive = false;
183
+ };
184
+ }, [platform, client]);
185
+ useEffect(() => {
186
+ if (!nodeId) {
187
+ setInstalled([]);
188
+ return;
189
+ }
190
+ let alive = true;
191
+ setInstalledLoading(true);
192
+ client.nodes.list().then((nodes) => {
193
+ if (!alive) return;
194
+ const n = nodes.find((x) => x.id === nodeId);
195
+ const v = n?.reported_caps?.installed_images;
196
+ setInstalled(Array.isArray(v) ? v.filter((s) => typeof s === "string") : []);
197
+ }).catch(() => {
198
+ if (alive) setInstalled([]);
199
+ }).finally(() => {
200
+ if (alive) setInstalledLoading(false);
201
+ });
202
+ return () => {
203
+ alive = false;
204
+ };
205
+ }, [nodeId, client]);
206
+ useEffect(() => {
207
+ let alive = true;
208
+ client.devices.profileCatalog().then((groups) => {
209
+ if (alive) setCatalog(groups);
210
+ }).catch(() => {
211
+ if (alive) setCatalog([]);
212
+ });
213
+ return () => {
214
+ alive = false;
215
+ };
216
+ }, [client]);
217
+ const availableVersions = nodeId ? versions.filter((o) => installed.includes(o.image_tag)) : versions;
218
+ const versionOptions = availableVersions.map((o) => ({ value: o.id, label: o.label }));
219
+ const versionsLoading = loading || installedLoading;
220
+ const noPlatformImages = versions.length === 0;
221
+ const selectedImage = spec.image_id ?? "";
222
+ useEffect(() => {
223
+ if (versionsLoading) return;
224
+ if (selectedImage && !availableVersions.some((o) => o.id === selectedImage)) {
225
+ patch({ image_id: void 0, os_version: void 0, android_version: void 0 });
226
+ }
227
+ }, [versionsLoading, selectedImage, availableVersions]);
228
+ const brandOptions = catalog.map((g) => ({ value: g.brand, label: g.label }));
229
+ const av = spec.android_version;
230
+ const modelsForBrand = useCallback(
231
+ (b) => catalog.find((g) => g.brand === b)?.models.filter((m) => !av || m.android_version === av).map((m) => m.model) ?? [],
232
+ [catalog, av]
233
+ );
234
+ const pickModel = useCallback(
235
+ (b) => {
236
+ const ms = modelsForBrand(b);
237
+ return ms.length ? ms[Math.floor(Math.random() * ms.length)] : void 0;
238
+ },
239
+ [modelsForBrand]
240
+ );
241
+ const setBrandValue = (b) => {
242
+ setBrand(b);
243
+ patch({ model: b ? pickModel(b) : void 0 });
244
+ };
245
+ useEffect(() => {
246
+ if (!brand) return;
247
+ patch({ model: pickModel(brand) });
248
+ }, [brand, av, catalog]);
249
+ useEffect(() => {
250
+ if (platform === "android" && (spec.frida === void 0 || spec.kernelsu === void 0)) {
251
+ patch({ frida: spec.frida ?? true, kernelsu: spec.kernelsu ?? true });
252
+ }
253
+ }, [platform]);
254
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
255
+ /* @__PURE__ */ jsxs("div", { children: [
256
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u5E73\u53F0 platform" }),
257
+ /* @__PURE__ */ jsx(
258
+ Select,
259
+ {
260
+ value: platform,
261
+ onChange: (v) => setPlatform(v),
262
+ options: PLATFORM_OPTIONS
263
+ }
264
+ )
265
+ ] }),
266
+ /* @__PURE__ */ jsxs("div", { children: [
267
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u7CFB\u7EDF\u7248\u672C version" }),
268
+ versionsLoading ? /* @__PURE__ */ jsx("div", { className: "text-body-sm text-muted-foreground py-2", children: "\u6B63\u5728\u52A0\u8F7D\u53EF\u7528\u7248\u672C\u2026" }) : versionOptions.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-body-sm text-muted-foreground py-2", children: noPlatformImages ? "\u8BE5\u5E73\u53F0\u6682\u65E0\u53EF\u7528\u7248\u672C\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u914D\u7F6E\u955C\u50CF\u3002" : "\u8BE5\u8282\u70B9\u672A\u5B89\u88C5\u6B64\u5E73\u53F0\u7684\u955C\u50CF\uFF0C\u8BF7\u6362\u4E00\u4E2A\u8282\u70B9\uFF0C\u6216\u5728\u8282\u70B9\u4E0A\u5B89\u88C5\u955C\u50CF\u540E\u91CD\u8BD5\u3002" }) : /* @__PURE__ */ jsx(
269
+ Select,
270
+ {
271
+ value: selectedImage,
272
+ onChange: setImage,
273
+ options: [{ value: "", label: "\u8BF7\u9009\u62E9\u7248\u672C" }, ...versionOptions]
274
+ }
275
+ )
276
+ ] }),
277
+ /* @__PURE__ */ jsxs("div", { children: [
278
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u89C4\u683C spec tier" }),
279
+ /* @__PURE__ */ jsx(
280
+ Select,
281
+ {
282
+ value: String(memory),
283
+ onChange: (v) => patch({ hardware: { ...spec.hardware, memory_mb: Number(v) } }),
284
+ options: TIER_OPTIONS
285
+ }
286
+ )
287
+ ] }),
288
+ platform === "android" && /* @__PURE__ */ jsxs("div", { children: [
289
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u56FD\u5BB6/\u5730\u533A region \xB7 SIM/\u8BED\u8A00/\u65F6\u533A" }),
290
+ /* @__PURE__ */ jsx(
291
+ Select,
292
+ {
293
+ value: spec.region ?? "us",
294
+ onChange: (v) => patch({ region: v }),
295
+ options: REGION_OPTIONS
296
+ }
297
+ )
298
+ ] }),
299
+ platform === "android" && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-border/40 p-2.5", children: [
300
+ /* @__PURE__ */ jsxs("div", { children: [
301
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u54C1\u724C brand \xB7 \u9501\u5B9A\u6307\u7EB9\u8EAB\u4EFD" }),
302
+ /* @__PURE__ */ jsx(
303
+ Select,
304
+ {
305
+ value: brand,
306
+ onChange: setBrandValue,
307
+ options: [
308
+ { value: "", label: "\u81EA\u52A8 auto(\u968F\u673A\u673A\u578B)" },
309
+ ...brandOptions
310
+ ]
311
+ }
312
+ )
313
+ ] }),
314
+ brand && modelsForBrand(brand).length === 0 && /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-amber-500/80", children: "\u5F53\u524D\u7CFB\u7EDF\u7248\u672C\u4E0B\u8BE5\u54C1\u724C\u6682\u65E0\u53EF\u7528\u673A\u578B,\u8BF7\u6362\u54C1\u724C\u6216\u8C03\u6574\u7CFB\u7EDF\u7248\u672C\u3002" }),
315
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/60", children: "\u53EA\u9700\u9009\u54C1\u724C:\u540E\u7AEF\u5C06\u5728\u8BE5\u54C1\u724C(\u5F53\u524D\u7CFB\u7EDF\u7248\u672C)\u4E0B\u968F\u673A\u6311\u4E00\u53F0\u673A\u578B\u4F5C\u4E3A\u6307\u7EB9\u8EAB\u4EFD base \u751F\u6210\u53C2\u6570(Build.MODEL/\u5382\u5546/CPU \u7B49\u4E00\u81F4)\u3002\u4E0D\u9009\u54C1\u724C\u5219\u8DE8\u54C1\u724C\u968F\u673A\u3002" })
316
+ ] }),
317
+ platform === "android" && /* @__PURE__ */ jsxs("div", { children: [
318
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "GPU \u6E32\u67D3 gpu" }),
319
+ /* @__PURE__ */ jsx(
320
+ Select,
321
+ {
322
+ value: spec.gpu_mode ?? "auto",
323
+ onChange: (v) => patch({
324
+ gpu_mode: v === "auto" ? void 0 : v
325
+ }),
326
+ options: GPU_OPTIONS
327
+ }
328
+ ),
329
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[10px] text-muted-foreground/60", children: "auto:\u8282\u70B9\u6709 GPU \u81EA\u52A8\u786C\u4EF6\u52A0\u901F,\u65E0 GPU \u81EA\u52A8\u8F6F\u4EF6\u6E32\u67D3\u3002\u4E00\u955C\u50CF\u901A\u5403,\u65E0\u9700\u4E3A GPU \u5355\u72EC\u51FA\u955C\u50CF\u3002" })
330
+ ] }),
331
+ platform === "android" && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground/60", children: "Android \u865A\u62DF\u673A\u9ED8\u8BA4\u5DF2\u542F\u7528 root(aixhide \u5185\u6838\u5C42\u9690\u85CF)\u4E0E Frida(\u9690\u85CF\u6CE8\u5165,\u9ED8\u8BA4\u5173\u95ED\u53EF\u6309\u9700\u542F\u52A8), \u7528\u4E8E\u81EA\u52A8\u5316/\u52A8\u6001 hook\uFF0C\u65E0\u9700\u5355\u72EC\u914D\u7F6E\u3002" })
332
+ ] });
333
+ }
334
+ function AddWirelessDeviceDialog({ open, onClose, nodeId: initialNode, platform: initialPlatform }) {
335
+ const client = useClient();
336
+ const { refetch: fetchDevices } = useDevices();
337
+ const [platform, setPlatform] = useState(initialPlatform ?? "android");
338
+ const [nodes, setNodes] = useState([]);
339
+ const [nodeId, setNodeId] = useState("");
340
+ const [error, setError] = useState("");
341
+ const [busy, setBusy] = useState(false);
342
+ const [scan, setScan] = useState(null);
343
+ const [manual, setManual] = useState(false);
344
+ const [addr, setAddr] = useState("");
345
+ const [code, setCode] = useState("");
346
+ const [iosDevices, setIosDevices] = useState(null);
347
+ useEffect(() => {
348
+ if (!open) return;
349
+ setPlatform(initialPlatform ?? "android");
350
+ setError("");
351
+ setScan(null);
352
+ setIosDevices(null);
353
+ setManual(false);
354
+ setAddr("");
355
+ setCode("");
356
+ (async () => {
357
+ try {
358
+ const rows = (await client.nodes.list()).filter((n) => n.status === "online");
359
+ setNodes(rows);
360
+ const preferred = rows.find((n) => n.id === initialNode) ?? rows[0];
361
+ setNodeId(preferred?.id ?? "");
362
+ } catch (e) {
363
+ setError(e.message);
364
+ }
365
+ })();
366
+ }, [open, initialNode, initialPlatform, client]);
367
+ const runScan = useCallback(async () => {
368
+ if (!nodeId) return;
369
+ setBusy(true);
370
+ setError("");
371
+ try {
372
+ if (platform === "android") {
373
+ setScan(await client.nodes.adbScan(nodeId));
374
+ } else {
375
+ setIosDevices((await client.nodes.iosScan(nodeId)).devices);
376
+ }
377
+ } catch (e) {
378
+ setError(friendly(e.message));
379
+ } finally {
380
+ setBusy(false);
381
+ }
382
+ }, [nodeId, platform, client]);
383
+ useEffect(() => {
384
+ if (open && nodeId && !manual) runScan();
385
+ }, [open, nodeId, platform, manual, runScan]);
386
+ const wrap = async (fn, close = false) => {
387
+ setBusy(true);
388
+ setError("");
389
+ try {
390
+ await fn();
391
+ await fetchDevices();
392
+ if (close) onClose();
393
+ else await runScan();
394
+ } catch (e) {
395
+ setError(friendly(e.message));
396
+ } finally {
397
+ setBusy(false);
398
+ }
399
+ };
400
+ const noNode = nodes.length === 0;
401
+ return /* @__PURE__ */ jsx(
402
+ Dialog,
403
+ {
404
+ open,
405
+ onClose,
406
+ title: "\u6DFB\u52A0\u65E0\u7EBF\u8BBE\u5907",
407
+ description: "Android \u8D70 ADB \u914D\u5BF9/\u8FDE\u63A5\uFF1BiOS \u8D70\u4FE1\u4EFB\u914D\u5BF9\u540E\u542F\u7528\u65E0\u7EBF\u3002",
408
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
409
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md border border-border bg-secondary p-0.5", children: [
410
+ /* @__PURE__ */ jsx(SegBtn, { active: platform === "android", onClick: () => setPlatform("android"), children: "Android" }),
411
+ /* @__PURE__ */ jsx(SegBtn, { active: platform === "ios", onClick: () => setPlatform("ios"), children: "iOS" })
412
+ ] }),
413
+ /* @__PURE__ */ jsxs("div", { children: [
414
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u8282\u70B9\uFF08\u4EC5\u5728\u7EBF\uFF09" }),
415
+ noNode ? /* @__PURE__ */ jsx("p", { className: "rounded bg-secondary px-2 py-1.5 text-[11px] text-muted-foreground", children: "\u65E0\u5728\u7EBF\u8282\u70B9\u3002\u8BF7\u5148\u5728\u76EE\u6807\u8282\u70B9\u542F\u52A8 aixauto daemon \u540E\u5237\u65B0\u3002" }) : /* @__PURE__ */ jsx(
416
+ Select,
417
+ {
418
+ value: nodeId,
419
+ onChange: setNodeId,
420
+ options: nodes.map((n) => ({ value: n.id, label: n.name })),
421
+ placeholder: "\u9009\u62E9\u8282\u70B9"
422
+ }
423
+ )
424
+ ] }),
425
+ !noNode && /* @__PURE__ */ jsxs(Fragment, { children: [
426
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
427
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: platform === "android" ? "\u8282\u70B9\u4E0A\u62A5\u7684\u65E0\u7EBF ADB \u8BBE\u5907" : "\u8282\u70B9\u8FDE\u63A5\u7684 iOS \u8BBE\u5907" }),
428
+ /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: runScan, children: [
429
+ /* @__PURE__ */ jsx(RefreshCw, { className: cn("h-3.5 w-3.5", busy && "animate-spin") }),
430
+ busy ? "\u626B\u63CF\u4E2D\u2026" : "\u626B\u63CF"
431
+ ] })
432
+ ] }),
433
+ platform === "android" ? manual ? /* @__PURE__ */ jsx(
434
+ AndroidManual,
435
+ {
436
+ addr,
437
+ code,
438
+ busy,
439
+ onAddr: setAddr,
440
+ onCode: setCode,
441
+ onPair: () => {
442
+ if (!addr.trim() || !code.trim())
443
+ return setError("\u5730\u5740\u548C\u914D\u5BF9\u7801\u5FC5\u586B");
444
+ wrap(() => client.nodes.adbPair(nodeId, addr.trim(), code.trim()), true);
445
+ },
446
+ onBack: () => setManual(false)
447
+ }
448
+ ) : /* @__PURE__ */ jsx(
449
+ AndroidDiscover,
450
+ {
451
+ scan,
452
+ busy,
453
+ onConnect: (e) => wrap(() => client.nodes.adbConnect(nodeId, `${e.host}:${e.port}`), true),
454
+ onManual: () => setManual(true)
455
+ }
456
+ ) : /* @__PURE__ */ jsx(
457
+ IosDiscover,
458
+ {
459
+ devices: iosDevices,
460
+ busy,
461
+ onTrust: (udid) => wrap(() => client.nodes.iosPair(nodeId, udid)),
462
+ onWifi: (udid) => wrap(() => client.nodes.iosWifi(nodeId, udid, true)),
463
+ onUse: () => {
464
+ void fetchDevices();
465
+ onClose();
466
+ }
467
+ }
468
+ )
469
+ ] }),
470
+ error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-destructive", children: error }),
471
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", onClick: onClose, children: "\u5173\u95ED" }) })
472
+ ] })
473
+ }
474
+ );
475
+ }
476
+ function AndroidDiscover({
477
+ scan,
478
+ busy,
479
+ onConnect,
480
+ onManual
481
+ }) {
482
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
483
+ /* @__PURE__ */ jsxs("div", { children: [
484
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] text-muted-foreground", children: "\u5DF2\u914D\u5BF9\u8BBE\u5907" }),
485
+ !scan || scan.paired.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground/70", children: busy ? "\u626B\u63CF\u4E2D\u2026" : "\u672A\u53D1\u73B0\u5DF2\u914D\u5BF9\u8BBE\u5907" }) : scan.paired.map((e) => /* @__PURE__ */ jsxs(
486
+ "div",
487
+ {
488
+ className: "flex items-center justify-between gap-2 rounded bg-secondary px-2 py-1",
489
+ children: [
490
+ /* @__PURE__ */ jsxs("code", { className: "text-[11px]", children: [
491
+ e.host,
492
+ ":",
493
+ e.port
494
+ ] }),
495
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onConnect(e), children: "\u8FDE\u63A5" })
496
+ ]
497
+ },
498
+ `${e.host}:${e.port}`
499
+ ))
500
+ ] }),
501
+ scan && scan.pairing.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
502
+ /* @__PURE__ */ jsx("p", { className: "mb-1 text-[11px] text-muted-foreground", children: "\u5F85\u914D\u5BF9\uFF08\u9700\u914D\u5BF9\u7801 \u2014 \u70B9\u4E0B\u65B9\u624B\u52A8\u914D\u5BF9\uFF09" }),
503
+ scan.pairing.map((e) => /* @__PURE__ */ jsxs(
504
+ "code",
505
+ {
506
+ className: "mb-1 block break-all rounded bg-secondary px-2 py-1 text-[11px]",
507
+ children: [
508
+ e.host,
509
+ ":",
510
+ e.port
511
+ ]
512
+ },
513
+ `${e.host}:${e.port}`
514
+ ))
515
+ ] }),
516
+ /* @__PURE__ */ jsx(TextLink, { onClick: onManual, children: "\u6CA1\u6709\u81EA\u52A8\u53D1\u73B0\uFF1F\u624B\u52A8\u8F93\u5165\u914D\u5BF9\u7801 \u2192" })
517
+ ] });
518
+ }
519
+ function AndroidManual({
520
+ addr,
521
+ code,
522
+ busy,
523
+ onAddr,
524
+ onCode,
525
+ onPair,
526
+ onBack
527
+ }) {
528
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
529
+ /* @__PURE__ */ jsxs("div", { children: [
530
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "IP:\u7AEF\u53E3\uFF08\u914D\u5BF9\u670D\u52A1\uFF09" }),
531
+ /* @__PURE__ */ jsx(Input, { value: addr, onChange: (e) => onAddr(e.target.value), placeholder: "192.168.1.50:37000" })
532
+ ] }),
533
+ /* @__PURE__ */ jsxs("div", { children: [
534
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u914D\u5BF9\u7801" }),
535
+ /* @__PURE__ */ jsx(Input, { value: code, onChange: (e) => onCode(e.target.value), placeholder: "123456" })
536
+ ] }),
537
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground/70", children: "\u914D\u5BF9\u6210\u529F\u540E\u4F1A\u81EA\u52A8\u8FDE\u63A5\u8BBE\u5907\uFF0C\u4F7F\u5176\u4E0A\u7EBF\u3002" }),
538
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
539
+ /* @__PURE__ */ jsx(Button, { size: "sm", disabled: busy, onClick: onPair, children: busy ? "\u914D\u5BF9\u4E2D\u2026" : "\u914D\u5BF9\u5E76\u8FDE\u63A5" }),
540
+ /* @__PURE__ */ jsx(TextLink, { onClick: onBack, children: "\u2190 \u8FD4\u56DE\u626B\u63CF\u5217\u8868" })
541
+ ] })
542
+ ] });
543
+ }
544
+ function IosDiscover({
545
+ devices,
546
+ busy,
547
+ onTrust,
548
+ onWifi,
549
+ onUse
550
+ }) {
551
+ if (!devices || devices.length === 0) {
552
+ return /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-[11px] text-muted-foreground", children: busy ? "\u626B\u63CF\u4E2D\u2026" : "\u672A\u53D1\u73B0 iOS \u8BBE\u5907\u3002USB \u8FDE\u63A5\u8BBE\u5907\u540E\u5237\u65B0\u91CD\u8BD5\u3002" });
553
+ }
554
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
555
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground/70", children: "\u65B0\u8BBE\u5907\u9700\u5148 USB \u8FDE\u63A5\u5E76\u5728\u8BBE\u5907\u4E0A\u70B9\u300C\u4FE1\u4EFB\u300D\uFF0C\u518D\u542F\u7528\u65E0\u7EBF\u5373\u53EF\u62D4\u7EBF\u4F7F\u7528\u3002" }),
556
+ devices.map((d) => /* @__PURE__ */ jsxs(
557
+ "div",
558
+ {
559
+ className: "flex items-center gap-2 rounded-md border border-border bg-card px-2 py-1.5",
560
+ children: [
561
+ /* @__PURE__ */ jsx(Smartphone, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
562
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
563
+ /* @__PURE__ */ jsx("code", { className: "block truncate text-[11px]", children: d.udid }),
564
+ /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center gap-1", children: [
565
+ d.usb && /* @__PURE__ */ jsxs(Badge, { variant: "info", children: [
566
+ /* @__PURE__ */ jsx(Usb, { className: "mr-0.5 h-3 w-3" }),
567
+ "USB"
568
+ ] }),
569
+ d.wireless && /* @__PURE__ */ jsxs(Badge, { variant: "success", children: [
570
+ /* @__PURE__ */ jsx(Wifi, { className: "mr-0.5 h-3 w-3" }),
571
+ "\u65E0\u7EBF"
572
+ ] })
573
+ ] })
574
+ ] }),
575
+ d.wireless ? /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: onUse, children: "\u4F7F\u7528" }) : /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [
576
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onTrust(d.udid), children: "\u4FE1\u4EFB\u914D\u5BF9" }),
577
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onWifi(d.udid), children: "\u542F\u7528\u65E0\u7EBF" })
578
+ ] })
579
+ ]
580
+ },
581
+ d.udid
582
+ ))
583
+ ] });
584
+ }
585
+ function SegBtn({
586
+ active,
587
+ onClick,
588
+ children
589
+ }) {
590
+ return /* @__PURE__ */ jsx(
591
+ "button",
592
+ {
593
+ type: "button",
594
+ onClick,
595
+ className: cn(
596
+ "flex-1 rounded px-2 py-1 text-body-sm transition-colors",
597
+ active ? "bg-card text-primary" : "text-muted-foreground hover:text-foreground"
598
+ ),
599
+ children
600
+ }
601
+ );
602
+ }
603
+ function TextLink({ onClick, children }) {
604
+ return /* @__PURE__ */ jsx(
605
+ "button",
606
+ {
607
+ type: "button",
608
+ onClick,
609
+ className: "text-[11px] text-primary underline-offset-2 hover:underline",
610
+ children
611
+ }
612
+ );
613
+ }
614
+ function friendly(msg) {
615
+ if (msg.includes("node send failed"))
616
+ return "\u8282\u70B9\u79BB\u7EBF\u6216\u672A\u8FDE\u63A5\uFF08node send failed\uFF09\u3002\u8BF7\u786E\u8BA4\u8BE5\u8282\u70B9\u7684 aixauto daemon \u6B63\u5728\u8FD0\u884C\u3002";
617
+ if (msg.includes("timeout")) return "\u8282\u70B9\u54CD\u5E94\u8D85\u65F6\uFF0C\u8BF7\u91CD\u8BD5\u6216\u68C0\u67E5\u8282\u70B9\u72B6\u6001\u3002";
618
+ return msg;
619
+ }
620
+ var STATUS_META = {
621
+ mine: { label: "\u5DF2\u62E5\u6709", variant: "success" },
622
+ claimable: { label: "\u53EF\u8BA4\u9886", variant: "info" },
623
+ unregistered: { label: "\u672A\u6CE8\u518C", variant: "warning" },
624
+ owned_other: { label: "\u4ED6\u4EBA\u5360\u7528", variant: "muted" }
625
+ };
626
+ function RealDiscoverStep({ nodeId, picked, onPick }) {
627
+ const client = useClient();
628
+ const [devices, setDevices] = useState([]);
629
+ const [busy, setBusy] = useState(false);
630
+ const [error, setError] = useState("");
631
+ const [showWireless, setShowWireless] = useState(false);
632
+ const scan = useCallback(async () => {
633
+ if (!nodeId) return;
634
+ setBusy(true);
635
+ setError("");
636
+ try {
637
+ setDevices((await client.nodes.discoverDevices(nodeId)).devices);
638
+ } catch (e) {
639
+ setError(e.message);
640
+ } finally {
641
+ setBusy(false);
642
+ }
643
+ }, [nodeId, client]);
644
+ useEffect(() => {
645
+ scan();
646
+ }, [scan]);
647
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
648
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
649
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "\u8282\u70B9\u4E0A\u62A5\u7684\u771F\u673A\uFF08USB / \u65E0\u7EBF ADB\uFF09" }),
650
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
651
+ /* @__PURE__ */ jsxs(
652
+ Button,
653
+ {
654
+ size: "sm",
655
+ variant: "ghost",
656
+ disabled: busy,
657
+ onClick: () => setShowWireless(true),
658
+ children: [
659
+ /* @__PURE__ */ jsx(Wifi, { className: "h-3.5 w-3.5" }),
660
+ "\u8FDE\u63A5\u65E0\u7EBF\u8BBE\u5907"
661
+ ]
662
+ }
663
+ ),
664
+ /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "secondary", disabled: busy, onClick: scan, children: [
665
+ /* @__PURE__ */ jsx(RefreshCw, { className: cn("h-3.5 w-3.5", busy && "animate-spin") }),
666
+ "\u5237\u65B0"
667
+ ] })
668
+ ] })
669
+ ] }),
670
+ error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-destructive", children: error }),
671
+ !error && devices.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-6 text-center text-body-sm text-muted-foreground", children: busy ? "\u626B\u63CF\u4E2D\u2026" : "\u672A\u53D1\u73B0\u8BBE\u5907\u3002\u8FDE\u63A5\u65E0\u7EBF\u8BBE\u5907\u6216\u5237\u65B0\u540E\u91CD\u8BD5\u3002" }) : /* @__PURE__ */ jsx("div", { className: "max-h-64 space-y-1.5 overflow-auto", children: devices.map((d) => {
672
+ const meta = STATUS_META[d.status];
673
+ const disabled = d.status === "owned_other";
674
+ const selected = picked?.external_id === d.external_id;
675
+ return /* @__PURE__ */ jsxs(
676
+ "button",
677
+ {
678
+ type: "button",
679
+ disabled,
680
+ onClick: () => onPick(d),
681
+ className: cn(
682
+ "flex w-full items-center gap-3 rounded-md border px-3 py-2 text-left transition-colors",
683
+ selected ? "border-primary bg-primary/5" : "border-border bg-card hover:border-primary/60",
684
+ disabled && "cursor-not-allowed opacity-50"
685
+ ),
686
+ children: [
687
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border bg-secondary text-muted-foreground", children: /* @__PURE__ */ jsx(Smartphone, { className: "h-4 w-4" }) }),
688
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
689
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-body-sm font-medium text-foreground", children: d.model || d.external_id }),
690
+ /* @__PURE__ */ jsxs("span", { className: "block truncate font-mono text-[11px] text-muted-foreground", children: [
691
+ d.external_id,
692
+ d.transport ? ` \xB7 ${d.transport}` : "",
693
+ d.state ? ` \xB7 ${d.state}` : ""
694
+ ] })
695
+ ] }),
696
+ /* @__PURE__ */ jsx(Badge, { variant: meta.variant, children: meta.label })
697
+ ]
698
+ },
699
+ d.external_id
700
+ );
701
+ }) }),
702
+ /* @__PURE__ */ jsx(
703
+ AddWirelessDeviceDialog,
704
+ {
705
+ open: showWireless,
706
+ nodeId,
707
+ onClose: () => {
708
+ setShowWireless(false);
709
+ scan();
710
+ }
711
+ }
712
+ )
713
+ ] });
714
+ }
715
+
716
+ // src/ui/devices/proxy.ts
717
+ function redactProxyUrl(value) {
718
+ const at = value.lastIndexOf("@");
719
+ if (at < 0) return value;
720
+ const credentials = value.slice(0, at);
721
+ const separator = credentials.lastIndexOf(":");
722
+ if (separator < 0) return value;
723
+ return `${credentials.slice(0, separator + 1)}***${value.slice(at)}`;
724
+ }
725
+ function isRedactedProxyUrl(value) {
726
+ return value.includes(":***@");
727
+ }
728
+ function isSupportedProxyUrl(value) {
729
+ if (!value) return true;
730
+ if (value.trim() !== value || !value.startsWith("http://") || value.includes("***")) return false;
731
+ const at = value.slice("http://".length).match(/@/g)?.length ?? 0;
732
+ if (at > 1) return false;
733
+ try {
734
+ const url = new URL(value);
735
+ return url.protocol === "http:" && (at === 0 || url.username.length > 0 && url.password.length > 0) && url.hostname.length > 0 && url.port.length > 0 && url.pathname === "/" && !url.search && !url.hash;
736
+ } catch {
737
+ return false;
738
+ }
739
+ }
740
+ function proxySummaryFromDevice(device) {
741
+ const value = device?.meta && typeof device.meta.proxy_summary === "object" ? device.meta.proxy_summary : null;
742
+ if (!value || Array.isArray(value)) return null;
743
+ const summary = value;
744
+ return {
745
+ configured: summary.configured === true,
746
+ desired_revision: typeof summary.desired_revision === "number" ? summary.desired_revision : null,
747
+ observed_revision: typeof summary.observed_revision === "number" ? summary.observed_revision : null,
748
+ observed_status: isProxyApplyStatus(summary.observed_status) ? summary.observed_status : null
749
+ };
750
+ }
751
+ function isProxyApplyStatus(value) {
752
+ return value === "pending" || value === "applied" || value === "failed" || value === "unsupported" || value === "unsupported_legacy";
753
+ }
754
+ function proxyCapabilityReason(device) {
755
+ if (device.platform !== "android" || device.meta?.provisioned !== true) {
756
+ return "Per-device transparent proxy requires a provisioned virtual Android Redroid device.";
757
+ }
758
+ if (!(device.node_id ?? device.nodeId) && !device.meta?.node_id) {
759
+ return "The owning node is unknown, so per-device proxy is unavailable.";
760
+ }
761
+ return "The owning node does not report per-device transparent proxy capability.";
762
+ }
763
+ function proxyProbeMessage(result) {
764
+ const message = result.message ?? result.error;
765
+ return typeof message === "string" && message.trim() ? message : null;
766
+ }
767
+ function proxyProbeExitIp(result) {
768
+ return result.exit_ip ?? result.egress_ip ?? null;
769
+ }
770
+ function isProxyProbeUnsupported(result) {
771
+ return result.enforcement === "unsupported" || result.error_code === "unsupported_runtime" || proxyProbeMessage(result)?.toLowerCase().includes("unsupported") === true;
772
+ }
773
+ function createProxyProbeGuard() {
774
+ let generation = 0;
775
+ let deviceId = null;
776
+ return {
777
+ sync(nextDeviceId) {
778
+ if (deviceId !== nextDeviceId) {
779
+ deviceId = nextDeviceId;
780
+ generation += 1;
781
+ }
782
+ },
783
+ begin(requestDeviceId) {
784
+ if (deviceId !== requestDeviceId) return null;
785
+ generation += 1;
786
+ return generation;
787
+ },
788
+ isCurrent(requestDeviceId, token) {
789
+ return deviceId === requestDeviceId && generation === token;
790
+ }
791
+ };
792
+ }
793
+ var LOCALE_OPTIONS = [
794
+ { value: "", label: "\u9ED8\u8BA4 (\u968F\u8282\u70B9)" },
795
+ { value: "zh-CN", label: "\u4E2D\u6587 (zh-CN)" },
796
+ { value: "en-US", label: "English (en-US)" },
797
+ { value: "ja-JP", label: "\u65E5\u672C\u8A9E (ja-JP)" },
798
+ { value: "ko-KR", label: "\uD55C\uAD6D\uC5B4 (ko-KR)" }
799
+ ];
800
+ function ProxySpoofStep({ spec, onChange, isReal }) {
801
+ const proxy = spec.proxy ?? "";
802
+ const locale = spec.config?.locale ?? "";
803
+ const proxyInvalid = proxy.trim().length > 0 && !isSupportedProxyUrl(proxy.trim());
804
+ const setProxy = (url) => {
805
+ const trimmed = url.trim();
806
+ if (!trimmed) {
807
+ const { proxy: _drop, ...rest } = spec;
808
+ onChange(rest);
809
+ return;
810
+ }
811
+ onChange({ ...spec, proxy: trimmed });
812
+ };
813
+ const setLocale = (v) => {
814
+ const config = { ...spec.config };
815
+ if (v) config.locale = v;
816
+ else delete config.locale;
817
+ const region = v ? v.split("-")[1]?.toLowerCase() : void 0;
818
+ onChange({ ...spec, config, region });
819
+ };
820
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
821
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
822
+ /* @__PURE__ */ jsx("p", { className: "text-label-caps text-muted-foreground", children: "\u4EE3\u7406 proxy" }),
823
+ /* @__PURE__ */ jsxs("div", { children: [
824
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u4EE3\u7406\u5730\u5740 URL" }),
825
+ /* @__PURE__ */ jsx(
826
+ Input,
827
+ {
828
+ value: proxy,
829
+ onChange: (e) => setProxy(e.target.value),
830
+ placeholder: "http://[user:password@]host:8080",
831
+ className: proxyInvalid ? "border-destructive" : void 0
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsxs("p", { className: "mt-1 text-[11px] text-muted-foreground", children: [
835
+ "\u53EF\u9009\u7684 HTTP CONNECT \u4EE3\u7406\uFF1A",
836
+ /* @__PURE__ */ jsx("code", { children: "http://[\u7528\u6237:\u5BC6\u7801@]\u4E3B\u673A:\u7AEF\u53E3" }),
837
+ "\u3002 \u5BC6\u7801\u4E2D\u7684 ",
838
+ /* @__PURE__ */ jsx("code", { children: "@" }),
839
+ "\u3001",
840
+ /* @__PURE__ */ jsx("code", { children: ":" }),
841
+ " \u7B49\u7279\u6B8A\u5B57\u7B26\u5FC5\u987B\u505A URL \u767E\u5206\u53F7\u7F16\u7801\uFF1BUDP/QUIC/DNS \u4E0D\u7ECF\u8FC7\u6B64\u4EE3\u7406\uFF0C\u4FDD\u6301\u76F4\u8FDE\u3002 \u7528\u6237\u540D\u548C\u5BC6\u7801\u53EF\u4E0D\u586B\uFF1B\u7559\u7A7A\u8868\u793A\u4E0D\u4F7F\u7528\u4EE3\u7406\u3002"
842
+ ] }),
843
+ proxyInvalid && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-destructive", children: "\u8BF7\u8F93\u5165\u6807\u51C6 http://[user:password@]host:port\uFF1B\u4E0D\u652F\u6301 socks/https\u3002" })
844
+ ] })
845
+ ] }),
846
+ /* @__PURE__ */ jsxs("section", { className: "space-y-2", children: [
847
+ /* @__PURE__ */ jsx("p", { className: "text-label-caps text-muted-foreground", children: "\u6307\u7EB9 spoof" }),
848
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "\u8BBE\u5907\u6307\u7EB9\u7531\u540E\u7AEF\u81EA\u52A8\u968F\u673A\u751F\u6210\uFF08device_profile\uFF09\u3002\u4E0B\u65B9\u4E3A\u53EF\u9009\u7684\u5730\u533A/\u8BED\u8A00\u63D0\u793A\u3002" }),
849
+ /* @__PURE__ */ jsxs("div", { children: [
850
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u5730\u533A/\u8BED\u8A00" }),
851
+ /* @__PURE__ */ jsx(
852
+ Select,
853
+ {
854
+ value: locale,
855
+ onChange: setLocale,
856
+ options: LOCALE_OPTIONS,
857
+ placeholder: "\u9ED8\u8BA4 (\u968F\u8282\u70B9)"
858
+ }
859
+ )
860
+ ] })
861
+ ] }),
862
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2 rounded-md border border-border bg-secondary px-3 py-2", children: [
863
+ /* @__PURE__ */ jsx(Info, { className: "mt-0.5 h-3.5 w-3.5 shrink-0 text-muted-foreground" }),
864
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: isReal ? "\u771F\u673A\u7684\u4EE3\u7406/\u6307\u7EB9\u914D\u7F6E\u4F1A\u88AB\u8BB0\u5F55\uFF0C\u4F46\u8282\u70B9\u4FA7\u5F3A\u5236\u8DEF\u7531\u4ECD\u5728\u5F00\u53D1\u4E2D\uFF08follow-up\uFF09\uFF0C\u6682\u4E0D\u4F1A\u5B9E\u9645\u751F\u6548\u3002" : "\u865A\u62DF\u8BBE\u5907\u7684\u4EE3\u7406\u968F provision \u89C4\u683C\u4E0B\u53D1\u5230\u8282\u70B9\u5E76\u5B8C\u6574\u751F\u6548\u3002" })
865
+ ] })
866
+ ] });
867
+ }
868
+ var emptySpec = { platform: "android" };
869
+ function DeviceCreate({ open, onClose, onCreated }) {
870
+ const client = useClient();
871
+ const { refetch: fetchDevices } = useDevices();
872
+ const { data: nodes } = useNodes();
873
+ const [step, setStep] = useState(0);
874
+ const [kind, setKind] = useState("virtual");
875
+ const [nodeId, setNodeId] = useState("");
876
+ const [spec, setSpec] = useState(emptySpec);
877
+ const [picked, setPicked] = useState(null);
878
+ const [busy, setBusy] = useState(false);
879
+ const [error, setError] = useState("");
880
+ const [quantity, setQuantity] = useState(1);
881
+ useEffect(() => {
882
+ if (!open) return;
883
+ setStep(0);
884
+ setKind("virtual");
885
+ setNodeId("");
886
+ setSpec(emptySpec);
887
+ setPicked(null);
888
+ setBusy(false);
889
+ setError("");
890
+ setQuantity(1);
891
+ }, [open]);
892
+ const proxyOk = isSupportedProxyUrl(spec.proxy?.trim() ?? "");
893
+ const canNext = (() => {
894
+ if (step === 0) return true;
895
+ if (step === 1) return !!nodeId;
896
+ if (step === 2) return kind === "virtual" ? true : !!picked;
897
+ return true;
898
+ })();
899
+ const enrollDiscovered = async (d) => {
900
+ if (d.status === "unregistered") {
901
+ await client.devices.register({
902
+ external_id: d.external_id,
903
+ kind: d.platform ?? void 0,
904
+ name: d.model ?? d.external_id
905
+ });
906
+ } else {
907
+ await client.devices.claim(d.external_id);
908
+ }
909
+ };
910
+ const finish = async () => {
911
+ setBusy(true);
912
+ setError("");
913
+ try {
914
+ let result = null;
915
+ if (kind === "virtual" && quantity > 1) {
916
+ const settled = await Promise.allSettled(
917
+ Array.from(
918
+ { length: quantity },
919
+ () => client.devices.provision({ ...spec, node_id: nodeId })
920
+ )
921
+ );
922
+ const fulfilled = settled.filter(
923
+ (r) => r.status === "fulfilled"
924
+ );
925
+ const rejected = settled.filter(
926
+ (r) => r.status === "rejected"
927
+ );
928
+ await fetchDevices();
929
+ if (rejected.length === 0) {
930
+ onCreated?.(fulfilled[0]?.value ?? null);
931
+ onClose();
932
+ return;
933
+ }
934
+ const reasons = [...new Set(rejected.map((r) => r.reason?.message ?? String(r.reason)))];
935
+ setError(`${fulfilled.length} \u53F0\u521B\u5EFA\u6210\u529F\uFF0C${rejected.length} \u53F0\u5931\u8D25\uFF1A${reasons.join("\uFF1B")}`);
936
+ return;
937
+ }
938
+ if (kind === "virtual") {
939
+ result = await client.devices.provision({ ...spec, node_id: nodeId });
940
+ } else if (picked) {
941
+ await enrollDiscovered(picked);
942
+ }
943
+ await fetchDevices();
944
+ onCreated?.(result);
945
+ onClose();
946
+ } catch (e) {
947
+ setError(e.message);
948
+ } finally {
949
+ setBusy(false);
950
+ }
951
+ };
952
+ const back = () => setStep((s) => s > 0 ? s - 1 : s);
953
+ const next = () => setStep((s) => s + 1);
954
+ return /* @__PURE__ */ jsx(
955
+ Dialog,
956
+ {
957
+ open,
958
+ onClose,
959
+ title: "\u521B\u5EFA\u8BBE\u5907",
960
+ description: "\u9009\u62E9\u865A\u62DF\u8BBE\u5907\u6216\u771F\u673A\uFF0C\u6311\u9009\u53EF\u7528\u8282\u70B9\uFF0C\u5E76\u914D\u7F6E\u4EE3\u7406\u4E0E\u6307\u7EB9\u3002",
961
+ maxWidth: "max-w-lg",
962
+ children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
963
+ /* @__PURE__ */ jsx(Stepper, { step, kind }),
964
+ error && /* @__PURE__ */ jsx("p", { className: "text-[11px] text-destructive", children: error }),
965
+ step === 0 && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
966
+ /* @__PURE__ */ jsx(
967
+ TypeCard,
968
+ {
969
+ active: kind === "virtual",
970
+ title: "\u865A\u62DF\u8BBE\u5907",
971
+ desc: "Android / iOS \u4E91\u8BBE\u5907\uFF0C\u6309\u89C4\u683C\u65B0\u5EFA",
972
+ onClick: () => setKind("virtual")
973
+ }
974
+ ),
975
+ /* @__PURE__ */ jsx(
976
+ TypeCard,
977
+ {
978
+ active: kind === "real",
979
+ title: "\u771F\u673A",
980
+ desc: "\u4ECE\u8282\u70B9\u5DF2\u4E0A\u62A5\u7684\u771F\u673A\u4E2D\u9009\u53D6",
981
+ onClick: () => setKind("real")
982
+ }
983
+ )
984
+ ] }),
985
+ step === 1 && /* @__PURE__ */ jsx(NodePicker, { value: nodeId, onChange: setNodeId, hidePlatform: kind === "real" }),
986
+ step === 2 && (kind === "virtual" ? /* @__PURE__ */ jsx(VirtualSpecStep, { spec, onChange: setSpec, nodeId }) : /* @__PURE__ */ jsx(RealDiscoverStep, { nodeId, picked, onPick: setPicked })),
987
+ step === 3 && /* @__PURE__ */ jsx(
988
+ ProxySpoofStep,
989
+ {
990
+ spec,
991
+ onChange: setSpec,
992
+ isReal: kind === "real"
993
+ }
994
+ ),
995
+ step === 3 && kind === "virtual" && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
996
+ /* @__PURE__ */ jsx("label", { className: "text-[11px] text-muted-foreground", children: "\u521B\u5EFA\u6570\u91CF" }),
997
+ /* @__PURE__ */ jsx(
998
+ "input",
999
+ {
1000
+ type: "number",
1001
+ min: 1,
1002
+ max: 20,
1003
+ value: quantity,
1004
+ onChange: (e) => setQuantity(Number(e.target.value)),
1005
+ className: "h-7 w-16 rounded-md border border-input bg-background px-2 text-[11px]"
1006
+ }
1007
+ ),
1008
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground/60", children: "\u4E00\u6B21\u5E76\u53D1\u521B\u5EFA\u591A\u53F0\u76F8\u540C\u89C4\u683C\u7684\u8BBE\u5907\uFF081\u201320\uFF09" })
1009
+ ] }),
1010
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t border-border pt-3", children: [
1011
+ /* @__PURE__ */ jsx(Button, { size: "sm", variant: "ghost", disabled: step === 0 || busy, onClick: back, children: "\u4E0A\u4E00\u6B65" }),
1012
+ step < 3 ? /* @__PURE__ */ jsx(Button, { size: "sm", disabled: !canNext, onClick: next, children: "\u4E0B\u4E00\u6B65" }) : /* @__PURE__ */ jsx(
1013
+ Button,
1014
+ {
1015
+ size: "sm",
1016
+ disabled: busy || !proxyOk || kind === "virtual" && (quantity < 1 || quantity > 20),
1017
+ onClick: finish,
1018
+ children: busy ? "\u521B\u5EFA\u4E2D\u2026" : quantity > 1 ? `\u521B\u5EFA ${quantity} \u53F0` : "\u521B\u5EFA"
1019
+ }
1020
+ )
1021
+ ] })
1022
+ ] })
1023
+ }
1024
+ );
1025
+ }
1026
+ function Stepper({ step, kind }) {
1027
+ const labels = ["\u7C7B\u578B", "\u8282\u70B9", kind === "virtual" ? "\u89C4\u683C" : "\u53D1\u73B0\u771F\u673A", "\u4EE3\u7406/\u6307\u7EB9"];
1028
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1.5", children: labels.map((label, i) => /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-1.5", children: [
1029
+ /* @__PURE__ */ jsx(
1030
+ "div",
1031
+ {
1032
+ className: cn(
1033
+ "flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[10px] font-bold",
1034
+ i <= step ? "bg-primary text-primary-foreground" : "bg-secondary text-muted-foreground"
1035
+ ),
1036
+ children: i + 1
1037
+ }
1038
+ ),
1039
+ /* @__PURE__ */ jsx(
1040
+ "span",
1041
+ {
1042
+ className: cn(
1043
+ "truncate text-[11px]",
1044
+ i === step ? "text-foreground" : "text-muted-foreground"
1045
+ ),
1046
+ children: label
1047
+ }
1048
+ )
1049
+ ] }, label)) });
1050
+ }
1051
+ function TypeCard({
1052
+ active,
1053
+ title,
1054
+ desc,
1055
+ onClick
1056
+ }) {
1057
+ return /* @__PURE__ */ jsxs(
1058
+ "button",
1059
+ {
1060
+ type: "button",
1061
+ onClick,
1062
+ className: cn(
1063
+ "flex flex-col gap-1 rounded-lg border p-4 text-left transition-colors",
1064
+ active ? "border-primary bg-primary/5" : "border-border bg-card hover:border-primary/60"
1065
+ ),
1066
+ children: [
1067
+ /* @__PURE__ */ jsx("span", { className: "text-body-md font-semibold text-foreground", children: title }),
1068
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] text-muted-foreground", children: desc })
1069
+ ]
1070
+ }
1071
+ );
1072
+ }
1073
+
1074
+ export { AddWirelessDeviceDialog, DeviceCreate, NodePicker, ProxySpoofStep, RealDiscoverStep, VirtualSpecStep, createProxyProbeGuard, isProxyApplyStatus, isProxyProbeUnsupported, isRedactedProxyUrl, isSupportedProxyUrl, proxyCapabilityReason, proxyProbeExitIp, proxyProbeMessage, proxySummaryFromDevice, redactProxyUrl };