import { useEffect, useState } from "react"; import { Link, useNavigate, useParams } from "react-router"; import { useDataProvider, useList, useOne } from "@refinedev/core"; import { Alert, Button, Card, CardContent, Copyable, DetailList, Field, Input, Modal, PageHeader, PageState, Select, StatusPill, conflictMessage, useUnsavedChanges, } from "./lib/ui.jsx"; import { useTitle } from "./lib/hooks.js"; const aliasPattern = /^[a-z0-9][a-z0-9-]{0,31}$/; const platformOptions = ["windows", "linux", "macos"].map((value) => ({ value, label: value, })); const brandOptions = ["Chrome", "Edge", "Opera", "Vivaldi"].map((value) => ({ value, label: value, })); const spoofingFields = ["font", "audio", "canvas", "clientrects", "gpu"]; const stateText = { created: "已创建", running: "运行中", exited: "已停止", restarting: "重启中", paused: "已暂停", missing: "未部署", }; const blockText = { account_revoked: "授权已撤销", account_paused: "账号已暂停", binding_missing: "绑定缺失", network_exit_missing: "固定出口缺失", network_exit_unhealthy: "固定出口不健康", runtime_stop_pending: "停止结果待确认", runtime_missing: "运行实例缺失", }; const scheduleBlock = (runtime) => runtime.schedule_status === "ready" ? "" : blockText[runtime.schedule_block_reason] || runtime.schedule_block_reason || "不可调度"; const createInitial = { name: "", alias: "", gateway: "", image_version: "", account_id: "", network_exit_id: "", seed: "", platform: "", platform_version: "", brand: "", brand_version: "", hardware_concurrency: "", lang: "", accept_lang: "", timezone: "", disable_spoofing: [], }; // 高级字段空值不提交,收敛到 seed 驱动。 function nextFingerprintSeed(runtimes) { const used = new Set( runtimes .map((runtime) => Number(runtime.fingerprint?.seed)) .filter((seed) => Number.isInteger(seed) && seed > 0), ); let seed = 1000; while (used.has(seed)) seed += 1; return String(seed); } function fingerprintPayload(form) { const payload = { seed: Number(form.seed) }; const advanced = { platform: form.platform, platform_version: form.platform_version, brand: form.brand, brand_version: form.brand_version, lang: form.lang, accept_lang: form.accept_lang, timezone: form.timezone, }; for (const [key, value] of Object.entries(advanced)) if (value !== "") payload[key] = value; if (form.hardware_concurrency !== "") payload.hardware_concurrency = Number(form.hardware_concurrency); if (form.disable_spoofing.length > 0) payload.disable_spoofing = form.disable_spoofing.join(","); return payload; } function StatePill({ state }) { const known = Object.hasOwn(stateText, state); return ( ); } function RuntimeActions({ runtime, busy, onAction }) { const running = runtime.state === "running"; const block = runtime.schedule_block_reason === "runtime_missing" ? "" : scheduleBlock(runtime); const startLabel = `启动 ${runtime.name}${block ? `(${block})` : ""}`; return (
); } function SchedulePill({ runtime }) { const block = scheduleBlock(runtime); return ( ); } function UpgradeDialog({ target, images, busy, onClose, onConfirm }) { const [version, setVersion] = useState(""); const enabled = images.filter((image) => image.enabled); useEffect(() => { setVersion(""); }, [target]); return ( } >

容器将按新镜像重建,Profile 数据卷与指纹参数保持不变。

update("name", event.target.value)} /> update("alias", event.target.value)} placeholder="如:shop-01" /> update("image_version", event.target.value) } options={enabledImages.map((item) => ({ value: item.version, label: item.version, }))} placeholder="选择版本" disabled={enabledImages.length === 0} /> update("seed", event.target.value)} /> update("network_exit_id", event.target.value) } options={healthyExits.map((item) => ({ value: item.id, label: `${item.id} · ${item.protocol}://${item.host}:${item.port}`, }))} placeholder="当前机器直连" emptyOption={{ value: "", label: "当前机器直连" }} />