cicy-desktop 2.1.164 → 2.1.166
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.
- package/.github/workflows/build-wsl-package.yml +11 -0
- package/package.json +1 -1
- package/src/backends/homepage-preload.js +3 -3
- package/src/backends/homepage-react/assets/{index-DpB75035.js → index-C8HjxypR.js} +14 -14
- package/src/backends/homepage-react/assets/{index-BwoV_5tD.css → index-CGcN1St7.css} +1 -1
- package/src/backends/homepage-react/index.html +2 -2
- package/src/backends/local-teams.js +8 -8
- package/src/backends/sidecar-ipc.js +72 -69
- package/src/i18n/locales/en.json +2 -4
- package/src/i18n/locales/fr.json +2 -4
- package/src/i18n/locales/ja.json +2 -4
- package/src/i18n/locales/zh-CN.json +2 -4
- package/src/main.js +3 -33
- package/src/sidecar/cicy-code.js +2 -2
- package/src/sidecar/colima-docker.js +14 -21
- package/src/sidecar/docker.js +1 -1
- package/src/sidecar/wsl-docker.js +19 -28
- package/workers/render/src/App.css +2 -8
- package/workers/render/src/App.jsx +28 -61
package/src/main.js
CHANGED
|
@@ -817,9 +817,6 @@ X-GNOME-Autostart-enabled=true
|
|
|
817
817
|
let _sidecarWatchdogTimer = null;
|
|
818
818
|
function startSidecarWatchdog({ intervalMs = 30_000 } = {}) {
|
|
819
819
|
if (_sidecarWatchdogTimer) return;
|
|
820
|
-
// 主人: native 退役,全平台只有 docker。:8008 由 docker daemon(sidecar-ipc reconcile)
|
|
821
|
-
// + 容器 --restart 保活,没有 native 要 watchdog 去检查/重启。直接不跑。
|
|
822
|
-
return;
|
|
823
820
|
let consecutiveFailures = 0;
|
|
824
821
|
let restartInFlight = false;
|
|
825
822
|
|
|
@@ -880,36 +877,9 @@ electronApp.whenReady().then(async () => {
|
|
|
880
877
|
setupAppIcons();
|
|
881
878
|
ensureDesktopLauncher();
|
|
882
879
|
ensureAutoLaunch();
|
|
883
|
-
//
|
|
884
|
-
//
|
|
885
|
-
cicyCodeSidecar
|
|
886
|
-
.start({ logPath: path.join(os.homedir(), "logs", "cicy-code-sidecar.log") })
|
|
887
|
-
.then((c) => { if (c) log.info(`[Sidecar] cicy-code spawned pid=${c.pid}`); })
|
|
888
|
-
.catch((e) => log.warn(`[Sidecar] cicy-code start failed: ${e.message}`));
|
|
889
|
-
startSidecarWatchdog();
|
|
890
|
-
|
|
891
|
-
// Auto-register the local sidecar as 本地团队 once :8008 answers (主人:
|
|
892
|
-
// "本地团队没有占位" — a fresh install must show its local team without any
|
|
893
|
-
// manual step). addTeam upserts by host:port + auto-fills api_token from
|
|
894
|
-
// global.json, so re-runs are no-ops; addTeam itself then triggers the
|
|
895
|
-
// cloud team register + gateway-key injection when logged in. A fresh boot
|
|
896
|
-
// may npm-seed the runtime first, so probe for up to ~90s before giving up.
|
|
897
|
-
(async () => {
|
|
898
|
-
const sidecarPort = Number(process.env.CICY_CODE_PORT || 8008);
|
|
899
|
-
const lt = require("./backends/local-teams");
|
|
900
|
-
for (let i = 0; i < 30; i++) {
|
|
901
|
-
try {
|
|
902
|
-
if (await cicyCodeSidecar.probeExisting(sidecarPort)) {
|
|
903
|
-
const r = await lt.addTeam({ base_url: `http://127.0.0.1:${sidecarPort}`, name: "本地团队" });
|
|
904
|
-
if (r && r.ok) log.info(`[Sidecar] local team ${r.upserted ? "refreshed" : "registered"} (${r.id})`);
|
|
905
|
-
else log.warn(`[Sidecar] local team auto-register failed: ${r && r.error}`);
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
} catch (e) { log.warn(`[Sidecar] local team auto-register error: ${e.message}`); }
|
|
909
|
-
await new Promise((res) => setTimeout(res, 3000));
|
|
910
|
-
}
|
|
911
|
-
log.warn(`[Sidecar] local team auto-register gave up — :${sidecarPort} never came up`);
|
|
912
|
-
})();
|
|
880
|
+
// 主人: native :8008 退役 —— 不再起本机 cicy-code、不再 watchdog 保活、不再把它
|
|
881
|
+
// 自动注册成「本地团队」。cicy-code 只在 docker 容器里跑(Docker 卡,:8009 那套不变,
|
|
882
|
+
// 由 sidecar-ipc 自己管理)。cicyCodeSidecar 仍保留供 :8008 探活/版本查询用。
|
|
913
883
|
|
|
914
884
|
// Backend launcher: app menu + IPC handlers. Menu adds a Backends top-level
|
|
915
885
|
// entry; IPC powers the launcher window (src/backends/launcher.html).
|
package/src/sidecar/cicy-code.js
CHANGED
|
@@ -100,8 +100,8 @@ async function startFromRuntime({ logPath, port }) {
|
|
|
100
100
|
async function start({ logPath, port = DEFAULT_PORT, force = false, version = null } = {}) {
|
|
101
101
|
if (child && !force) return child;
|
|
102
102
|
|
|
103
|
-
// 主人: native
|
|
104
|
-
//
|
|
103
|
+
// 主人: native :8008 退役 —— 不再在本机起 cicy-code。docker team(:8009 容器)不变,
|
|
104
|
+
// 用户用那张 Docker 卡。这里直接返回,native 一律不起。
|
|
105
105
|
return null;
|
|
106
106
|
|
|
107
107
|
if (!force && await probeExisting(port)) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Docker 引擎」的方案,在 Mac 上就是 Colima:一个 Apache-2.0、纯 CLI、无 GUI、
|
|
5
5
|
// 无授权、无 root 的轻量 Linux VM(基于 Lima),里面跑标准 Docker 引擎。Lima 会
|
|
6
6
|
// 把 VM 内监听 127.0.0.1 的端口自动转发到宿主 127.0.0.1,所以 VM 里发布在
|
|
7
|
-
// :
|
|
7
|
+
// :8009 的容器,Mac 上 127.0.0.1:8009 直接可达——和 WSL2 的 localhost 转发等价。
|
|
8
8
|
//
|
|
9
9
|
// 与 wsl-docker.js 完全同接口(bootstrap/status/restart/stop/dockerRestart/
|
|
10
10
|
// recreate/update/upgrade/runContainer/readContainerToken),由 sidecar-ipc.js
|
|
@@ -220,7 +220,7 @@ const probeHealth = docker.probeHealth;
|
|
|
220
220
|
// • Apple Silicon 加 --platform linux/amd64(rosetta 跑 x86 容器);
|
|
221
221
|
// • /home/cicy 必须用 docker **named volume**,不能用 host bind-mount!真机实测:
|
|
222
222
|
// bind-mount 会用空的宿主目录**遮住镜像里预装的 /home/cicy**(cicy-code 装在那),
|
|
223
|
-
// entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:
|
|
223
|
+
// entrypoint 找不到就试图全局 npm 重装 → EACCES 崩溃,:8009 起不来。named volume
|
|
224
224
|
// 首次挂载会**从镜像内容预填充**,容器才看得到预装的 cicy-code(和 WSL 一致)。
|
|
225
225
|
// Shared folder bind: the CURRENT macOS user's ~/Desktop/Share ↔
|
|
226
226
|
// /home/cicy/cicy-ai/Share in the container. Auto-created (with a readme) on every
|
|
@@ -255,7 +255,7 @@ function shareMountArg() {
|
|
|
255
255
|
} catch { return ""; }
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
async function runContainer({ port =
|
|
258
|
+
async function runContainer({ port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
|
|
259
259
|
if (await probeHealth(port)) return { adopted: true };
|
|
260
260
|
try { await dk(`rm -f ${container}`, { timeout: 20000 }); } catch {} // 替换同名残留容器
|
|
261
261
|
const envArgs = Object.entries(env || {})
|
|
@@ -273,7 +273,7 @@ async function runContainer({ port = 8008, container = "cicy-code-docker-8008",
|
|
|
273
273
|
// 该卷的 _data/cicy-ai/global.json(最稳,避开 busy 容器的慢 exec);② 退回 docker
|
|
274
274
|
// exec。重试到 entrypoint 把 global.json 写出来为止;真读不到返回 ""(调用方不得拿
|
|
275
275
|
// 错/宿主 token 去开,会卡登录)。
|
|
276
|
-
async function readContainerToken(port =
|
|
276
|
+
async function readContainerToken(port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009") {
|
|
277
277
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
278
278
|
try { const { stdout } = await sh(`colima ssh -p ${PROFILE} -- sudo cat /var/lib/docker/volumes/${volume}/_data/cicy-ai/global.json 2>/dev/null`, { timeout: 10000 });
|
|
279
279
|
const m = String(stdout).match(/"api_token"\s*:\s*"(cicy_[A-Za-z0-9]+)"/);
|
|
@@ -324,7 +324,7 @@ async function ensureDesktopShortcut(_volume, _port) { /* no-op on darwin: named
|
|
|
324
324
|
// 与 wsl status 同形 { wsl, distro, engineUp, running },供 sidecar-ipc 复用:
|
|
325
325
|
// wsl → 平台/依赖就绪(brew+colima+docker CLI 都在)
|
|
326
326
|
// distro → VM(profile)已创建
|
|
327
|
-
async function status(port =
|
|
327
|
+
async function status(port = 8009) {
|
|
328
328
|
const deps = (await colimaInstalled()) && (await dockerCliInstalled());
|
|
329
329
|
const vm = deps && (await vmExists());
|
|
330
330
|
const up = vm && (await engineUp());
|
|
@@ -344,7 +344,7 @@ async function bootstrap(opts = {}) {
|
|
|
344
344
|
return _bootstrapInFlight;
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
async function _bootstrap({ onProgress, port =
|
|
347
|
+
async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
|
|
348
348
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
349
349
|
|
|
350
350
|
// 0) 快路径:已健康 → 秒返回(幂等)。
|
|
@@ -396,7 +396,7 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
|
|
|
396
396
|
// ---- 生命周期(卡片 ⋯ 菜单)--------------------------------------------
|
|
397
397
|
|
|
398
398
|
// 仅重启容器内的 cicy-code 进程(supervisor),cron/sshd 等不动;退回整容器重启。
|
|
399
|
-
async function restart({ container = "cicy-code-docker-
|
|
399
|
+
async function restart({ container = "cicy-code-docker-8009", port = 8009, volume = "cicy-team-8009" } = {}) {
|
|
400
400
|
await startVM({});
|
|
401
401
|
try { await dk(`exec ${container} supervisorctl -c /etc/supervisor/supervisord.conf restart cicy-code`, { timeout: 30000 }); }
|
|
402
402
|
catch { try { await dk(`restart ${container}`, { timeout: 60000 }); } catch {} }
|
|
@@ -406,7 +406,7 @@ async function restart({ container = "cicy-code-docker-8008", port = 8008, volum
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
// 原地更新 cicy-code(镜像内的 cicy-code-update.sh,不重建容器)。
|
|
409
|
-
async function update({ onProgress, container = "cicy-code-docker-
|
|
409
|
+
async function update({ onProgress, container = "cicy-code-docker-8009", port = 8009 } = {}) {
|
|
410
410
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
411
411
|
await startVM({});
|
|
412
412
|
emit({ phase: "image", status: "running", message: "更新 cicy-code(拉取最新版)…" });
|
|
@@ -415,23 +415,23 @@ async function update({ onProgress, container = "cicy-code-docker-8008", port =
|
|
|
415
415
|
{ emit, phase: "image", timeout: 300000 });
|
|
416
416
|
} catch (e) { emit({ phase: "done", status: "error", message: `更新失败:${e.message}(试试「升级」重装)` }); return { ok: false, reason: "update_failed" }; }
|
|
417
417
|
const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
|
|
418
|
-
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :
|
|
418
|
+
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8009 还没响应——稍等或点重试" });
|
|
419
419
|
return { ok: healthy };
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
async function stop({ container = "cicy-code-docker-
|
|
422
|
+
async function stop({ container = "cicy-code-docker-8009" } = {}) {
|
|
423
423
|
try { await dk(`stop ${container}`, { timeout: 30000 }); } catch {}
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
// docker restart 整个容器(entrypoint 重跑、重读 volume global.json)。
|
|
427
|
-
async function dockerRestart({ container = "cicy-code-docker-
|
|
427
|
+
async function dockerRestart({ container = "cicy-code-docker-8009" } = {}) {
|
|
428
428
|
await dk(`restart ${container}`, { timeout: 45000 });
|
|
429
429
|
return true;
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// 重建:强删占该端口的任何容器 + 目标容器,再 docker run(用新 env,如新 docker team
|
|
433
433
|
// 网关 key)。保留 bind-mount 宿主目录(数据/api_token 不丢)。破坏性 → 调用方要 confirm。
|
|
434
|
-
async function recreate({ port =
|
|
434
|
+
async function recreate({ port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
|
|
435
435
|
try { await dk(`ps -aq --filter publish=${port} | xargs -r docker --context ${CTX} rm -f 2>/dev/null; docker --context ${CTX} rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
|
|
436
436
|
const r = await runContainer({ port, container, volume, env });
|
|
437
437
|
try { await ensureDesktopShortcut(volume, port); } catch {}
|
|
@@ -439,21 +439,14 @@ async function recreate({ port = 8008, container = "cicy-code-docker-8008", volu
|
|
|
439
439
|
}
|
|
440
440
|
|
|
441
441
|
// 升级 = 删 VM 重装(重置;cicy-team 数据随之重置,实例重新 seed 出新 token)。
|
|
442
|
-
async function upgrade({ onProgress, port =
|
|
442
|
+
async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
|
|
443
443
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
444
444
|
emit({ phase: "install-docker", status: "running", message: "升级 = 重装运行环境(会重置容器数据)…" });
|
|
445
445
|
try { await sh(`colima delete -f -p ${PROFILE} 2>/dev/null; true`, { timeout: 120000 }); } catch {}
|
|
446
446
|
return await _bootstrap({ onProgress, port, container, volume, env });
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
-
// 容器 env 里有没有网关 LLM key —— daemon 用它判断运行中的容器是否需要"带 key 重建"
|
|
450
|
-
// (容器可能在登录前就先无 key 起来了)。key 未设时 printenv 非零退出 → catch → false。
|
|
451
|
-
async function hasGatewayKey(container = "cicy-code-docker-8008") {
|
|
452
|
-
try { const { stdout } = await dk(`exec ${container} printenv CICY_AI_GATEWAY_LLM_API_KEY`, { timeout: 8000 }); return /sk-/.test(String(stdout || "")); }
|
|
453
|
-
catch { return false; }
|
|
454
|
-
}
|
|
455
|
-
|
|
456
449
|
module.exports = {
|
|
457
450
|
bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
|
|
458
|
-
vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth,
|
|
451
|
+
vmExists, colimaInstalled, dockerCliInstalled, engineUp, imagePresent, probeHealth,
|
|
459
452
|
};
|
package/src/sidecar/docker.js
CHANGED
|
@@ -441,7 +441,7 @@ function desktopDir() {
|
|
|
441
441
|
// Start the container. Returns a sidecar child token { docker:true, container,
|
|
442
442
|
// id } or null when Docker isn't ready (homepage guides the user to install
|
|
443
443
|
// Docker Desktop). `container`/`volume` are parameterized so a SECOND instance
|
|
444
|
-
// (the Docker-版 cicy-code on :
|
|
444
|
+
// (the Docker-版 cicy-code on :8009) can run alongside the native local one
|
|
445
445
|
// without a name/volume collision.
|
|
446
446
|
async function start({ port = 8008, container = CONTAINER, volume = VOLUME, mountTarget = "/home/cicy/cicy-ai", env = {} } = {}) {
|
|
447
447
|
// Something already serves a healthy cicy-code on :port (a legacy-named
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
// (Apache-2.0, no licensing) INSIDE a WSL2 Ubuntu distro and drive it with
|
|
5
5
|
// deterministic Linux commands — no UAC click-through, no whale-icon wait, no
|
|
6
6
|
// leftover-staging / PATH issues. WSL2 forwards localhost, so a container
|
|
7
|
-
// published on :
|
|
7
|
+
// published on :8009 in Ubuntu is reachable at 127.0.0.1:8009 on Windows.
|
|
8
8
|
//
|
|
9
9
|
// Flow: ensure WSL2 → ensure Ubuntu distro → apt install docker.io → start
|
|
10
10
|
// dockerd → docker load (image tarball from ~/Downloads via /mnt/c) → docker run
|
|
11
|
-
// → health-probe :
|
|
11
|
+
// → health-probe :8009 from Windows. Every step checks-then-acts and is
|
|
12
12
|
// idempotent, so 重试 resumes.
|
|
13
13
|
|
|
14
14
|
const { execFile, execFileSync, spawn } = require("child_process");
|
|
@@ -411,7 +411,7 @@ function shareMountArg() {
|
|
|
411
411
|
} catch (e) { log.warn(`[wsl-docker] Share mount setup failed: ${e.message}`); return ""; }
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
async function runContainer({ port =
|
|
414
|
+
async function runContainer({ port = 8009, container = "cicy-code-docker", volume = "cicy-team-8009", env = {} } = {}) {
|
|
415
415
|
// 每次容器"启动"(含已在跑被 adopt)都确保桌面快捷方式存在 —— 不存在就建,坏了就修。
|
|
416
416
|
if (await probeHealth(port)) { ensureDesktopShortcut(volume, port).catch(() => {}); return { adopted: true }; }
|
|
417
417
|
// Replace any stale same-named container.
|
|
@@ -423,7 +423,7 @@ async function runContainer({ port = 8008, container = "cicy-code-docker", volum
|
|
|
423
423
|
// --dns: WSL2's auto resolv.conf points the distro at the host NAT gateway
|
|
424
424
|
// (172.x.x.1), which docker's default DNS forwarding does NOT reach from inside a
|
|
425
425
|
// bridge container → every lookup is EAI_AGAIN and cicy-code's startup `npm i`
|
|
426
|
-
// crash-loops the container (:
|
|
426
|
+
// crash-loops the container (:8009 never comes up). Pin public resolvers: Aliyun
|
|
427
427
|
// 223.5.5.5 (CN-fast) first, Google 8.8.8.8 as the overseas fallback.
|
|
428
428
|
const cmd = `docker run -d --name ${container} --restart unless-stopped --dns 223.5.5.5 --dns 8.8.8.8 -p 127.0.0.1:${port}:8008 -e CICY_PUBLIC=1 -v ${volume}:/home/cicy ${shareMountArg()} ${envArgs} ${IMAGE}`;
|
|
429
429
|
await wslRun(cmd, { timeout: 60000 });
|
|
@@ -432,11 +432,11 @@ async function runContainer({ port = 8008, container = "cicy-code-docker", volum
|
|
|
432
432
|
}
|
|
433
433
|
|
|
434
434
|
// Read the container's OWN api_token (its volume-persisted global.json). This is
|
|
435
|
-
// the ONLY correct credential for :
|
|
436
|
-
//
|
|
435
|
+
// the ONLY correct credential for :8009 — the host's 8008 token is different and
|
|
436
|
+
// 8009 rejects it. Retries because right after start the entrypoint may not have
|
|
437
437
|
// written global.json yet; returns "" only if it truly can't be read (callers
|
|
438
438
|
// must then NOT open with a wrong/host token — that strands the user at login).
|
|
439
|
-
async function readContainerToken(port =
|
|
439
|
+
async function readContainerToken(port = 8009, container = "cicy-code-docker", volume = "cicy-team-8009") {
|
|
440
440
|
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
441
441
|
// 1) Fast + reliable: read the volume-backed global.json straight from the
|
|
442
442
|
// distro fs. `docker exec` into a just-loaded/busy container is slow and
|
|
@@ -459,7 +459,7 @@ async function readContainerToken(port = 8008, container = "cicy-code-docker", v
|
|
|
459
459
|
}
|
|
460
460
|
|
|
461
461
|
// Register a Windows logon task that starts dockerd in our distro on every
|
|
462
|
-
// logon — old inbox WSL ignores wsl.conf [boot], so without this :
|
|
462
|
+
// logon — old inbox WSL ignores wsl.conf [boot], so without this :8009 is dead
|
|
463
463
|
// after a Windows reboot until the user clicks 启动. The container's
|
|
464
464
|
// --restart unless-stopped then brings cicy-code back automatically. Idempotent
|
|
465
465
|
// (start-dockerd.sh is `pgrep dockerd || dockerd`); /f overwrites a stale task.
|
|
@@ -477,7 +477,7 @@ function ensureAutostart() {
|
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
// Drop a desktop shortcut (folder icon) to the container's /home/cicy — i.e. the
|
|
480
|
-
// cicy-team volume on the distro — so the user can browse :
|
|
480
|
+
// cicy-team volume on the distro — so the user can browse :8009's files from
|
|
481
481
|
// Windows Explorer. \\wsl$\<distro>\… is the UNC view of the WSL filesystem.
|
|
482
482
|
// Idempotent: CreateShortcut overwrites. Best-effort (errors swallowed).
|
|
483
483
|
// PowerShell single-quoted literal. PowerShell does NOT treat backslash as an
|
|
@@ -487,7 +487,7 @@ function ensureAutostart() {
|
|
|
487
487
|
// backslashes) which Explorer can't open. That was the broken "WSL 快捷方式".
|
|
488
488
|
function psSingle(s) { return "'" + String(s).replace(/'/g, "''") + "'"; }
|
|
489
489
|
|
|
490
|
-
function ensureDesktopShortcut(volume = "cicy-team-
|
|
490
|
+
function ensureDesktopShortcut(volume = "cicy-team-8009", port = 8009) {
|
|
491
491
|
if (process.platform !== "win32") return Promise.resolve();
|
|
492
492
|
return new Promise((res) => {
|
|
493
493
|
// 快捷方式名带 port —— 多个 docker(不同端口)各自一个桌面文件夹快捷方式。
|
|
@@ -511,7 +511,7 @@ function ensureDesktopShortcut(volume = "cicy-team-8008", port = 8008) {
|
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
// Composite status for the card.
|
|
514
|
-
async function status(port =
|
|
514
|
+
async function status(port = 8009) {
|
|
515
515
|
// `unknown` = WSL didn't answer a probe (stuck/booting). The homepage uses it
|
|
516
516
|
// to show 「检测中/WSL 无响应·重试」instead of falsely showing 「下载安装」.
|
|
517
517
|
const miss = await docker.wslMissing(); // true | false | null(unknown)
|
|
@@ -542,7 +542,7 @@ async function bootstrap(opts = {}) {
|
|
|
542
542
|
return _bootstrapInFlight;
|
|
543
543
|
}
|
|
544
544
|
|
|
545
|
-
async function _bootstrap({ onProgress, port =
|
|
545
|
+
async function _bootstrap({ onProgress, port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
|
|
546
546
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
547
547
|
|
|
548
548
|
// Structured, PERSISTED trace of the whole run (electron-log → main.log) so a
|
|
@@ -650,7 +650,7 @@ async function _bootstrap({ onProgress, port = 8008, container = "cicy-code-dock
|
|
|
650
650
|
// Restart ONLY cicy-code via supervisor — cron / sshd / user daemons keep
|
|
651
651
|
// running (that's the whole point of the supervisor layout). Falls back to a
|
|
652
652
|
// full container restart on the pre-supervisor image.
|
|
653
|
-
async function restart({ container = "cicy-code-docker", port =
|
|
653
|
+
async function restart({ container = "cicy-code-docker", port = 8009, volume = "cicy-team-8009" } = {}) {
|
|
654
654
|
await startEngine();
|
|
655
655
|
try {
|
|
656
656
|
await wslRun(`docker exec ${container} supervisorctl -c /etc/supervisor/supervisord.conf restart cicy-code`, { timeout: 30000 });
|
|
@@ -666,7 +666,7 @@ async function restart({ container = "cicy-code-docker", port = 8008, volume = "
|
|
|
666
666
|
// which installs the latest version side-by-side, repoints the symlink, and
|
|
667
667
|
// `supervisorctl restart cicy-code` — no container recreate, daemons untouched.
|
|
668
668
|
// Streamed to the drawer so the user sees the npm pull + restart.
|
|
669
|
-
async function update({ onProgress, container = "cicy-code-docker", port =
|
|
669
|
+
async function update({ onProgress, container = "cicy-code-docker", port = 8009 } = {}) {
|
|
670
670
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
671
671
|
await startEngine();
|
|
672
672
|
emit({ phase: "image", status: "running", message: "更新 cicy-code(拉取最新版)…" });
|
|
@@ -678,7 +678,7 @@ async function update({ onProgress, container = "cicy-code-docker", port = 8008
|
|
|
678
678
|
return { ok: false, reason: "update_failed" };
|
|
679
679
|
}
|
|
680
680
|
const healthy = await docker.waitUntil(() => probeHealth(port), { totalMs: 120000, everyMs: 3000 });
|
|
681
|
-
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :
|
|
681
|
+
emit({ phase: "done", status: healthy ? "done" : "error", message: healthy ? "cicy-code 已更新到最新 🎉" : "更新了但 :8009 还没响应——稍等或点重试" });
|
|
682
682
|
return { ok: healthy };
|
|
683
683
|
}
|
|
684
684
|
async function stop({ container = "cicy-code-docker" } = {}) {
|
|
@@ -688,7 +688,7 @@ async function stop({ container = "cicy-code-docker" } = {}) {
|
|
|
688
688
|
// docker restart 整个容器(stop+start 同一个容器)—— 区别于 restart()(supervisorctl
|
|
689
689
|
// 重启容器内的 cicy-code 进程)和重建(rm+run)。容器重启后 entrypoint 重跑,会重读
|
|
690
690
|
// volume global.json,所以若先把新 key 写进 volume,这个就能让 cicy-code 用上新 key。
|
|
691
|
-
async function dockerRestart({ container = "cicy-code-docker-
|
|
691
|
+
async function dockerRestart({ container = "cicy-code-docker-8009" } = {}) {
|
|
692
692
|
await wslRun(`docker restart ${container}`, { timeout: 45000 });
|
|
693
693
|
return true;
|
|
694
694
|
}
|
|
@@ -696,7 +696,7 @@ async function dockerRestart({ container = "cicy-code-docker-8008" } = {}) {
|
|
|
696
696
|
// 重建容器:docker rm -f 旧容器 + docker run 新容器(用新 env,如新的 docker team 网关
|
|
697
697
|
// key)。**保留 volume**(数据/api_token/deviceId 不丢),只是换掉容器本身 + env。
|
|
698
698
|
// 破坏性(短暂中断 + 换 key)→ 调用方要 confirm。
|
|
699
|
-
async function recreate({ port =
|
|
699
|
+
async function recreate({ port = 8009, container = "cicy-code-docker-8009", volume = "cicy-team-8009", env = {} } = {}) {
|
|
700
700
|
// 强删占用该端口的**任何**容器(含老名字 cicy-code-docker)+ 目标容器 —— 否则
|
|
701
701
|
// runContainer 开头的 probeHealth 看到旧容器还健康会 adopt 它、不重建,key 就换不了。
|
|
702
702
|
try { await wslRun(`docker ps -aq --filter publish=${port} | xargs -r docker rm -f 2>/dev/null; docker rm -f ${container} 2>/dev/null; true`, { timeout: 30000 }); } catch {}
|
|
@@ -718,7 +718,7 @@ function unregisterDistro() {
|
|
|
718
718
|
// downloader, which copes with the flaky CN DNS that bare curl can't) is the
|
|
719
719
|
// only reliable CN update path. This RESETS the distro: the cicy-team volume is
|
|
720
720
|
// re-created and the instance re-seeds (new token) on next boot.
|
|
721
|
-
async function upgrade({ onProgress, port =
|
|
721
|
+
async function upgrade({ onProgress, port = 8009, container = "cicy-code-docker", volume = "cicy-team", env = {} } = {}) {
|
|
722
722
|
const emit = (ev) => { try { onProgress && onProgress(ev); } catch {} };
|
|
723
723
|
emit({ phase: "install-docker", status: "running", message: "升级 = 拉取最新运行环境并重装(会重置容器数据)…" });
|
|
724
724
|
try { await stop({ container }); } catch {}
|
|
@@ -727,16 +727,7 @@ async function upgrade({ onProgress, port = 8008, container = "cicy-code-docker"
|
|
|
727
727
|
return await _bootstrap({ onProgress, port, container, volume, env });
|
|
728
728
|
}
|
|
729
729
|
|
|
730
|
-
// 容器 env 里有没有网关 LLM key —— daemon 判断是否需要带 key 重建(同 colima-docker)。
|
|
731
|
-
async function hasGatewayKey(container = "cicy-code-docker") {
|
|
732
|
-
try {
|
|
733
|
-
const r = await wslRun(`docker exec ${container} printenv CICY_AI_GATEWAY_LLM_API_KEY`, { timeout: 8000 });
|
|
734
|
-
const s = (r && r.stdout != null) ? r.stdout : (r || "");
|
|
735
|
-
return /sk-/.test(String(s));
|
|
736
|
-
} catch { return false; }
|
|
737
|
-
}
|
|
738
|
-
|
|
739
730
|
module.exports = {
|
|
740
731
|
bootstrap, status, restart, stop, dockerRestart, recreate, update, upgrade, runContainer, readContainerToken,
|
|
741
|
-
distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun,
|
|
732
|
+
distroInstalled, dockerInstalled, dockerEngineUp, imagePresent, probeHealth, wslRun,
|
|
742
733
|
};
|
|
@@ -839,14 +839,8 @@ body {
|
|
|
839
839
|
.bcard__menu-item.is-accent:hover { background: var(--accent-soft); color: #c7dbff; }
|
|
840
840
|
.bcard__menu-item.is-danger { color: #f7a3a3; }
|
|
841
841
|
.bcard__menu-item.is-danger:hover { background: rgba(239,68,68,.16); color: #fff; }
|
|
842
|
-
/*
|
|
843
|
-
.bcard__menu-sep {
|
|
844
|
-
display: flex; align-items: center; gap: 8px;
|
|
845
|
-
margin: 5px 0 2px; padding: 0 12px;
|
|
846
|
-
font-size: 10.5px; letter-spacing: .04em; text-transform: uppercase;
|
|
847
|
-
color: #6b7280; user-select: none;
|
|
848
|
-
}
|
|
849
|
-
.bcard__menu-sep::after { content: ""; flex: 1; height: 1px; background: rgba(255,255,255,.08); }
|
|
842
|
+
/* Plain divider line in the DockerCard ⋯ menu (cicy-code ops 上 / Docker 容器 ops 下). */
|
|
843
|
+
.bcard__menu-sep { height: 1px; margin: 5px 10px; background: rgba(255,255,255,.08); }
|
|
850
844
|
/* Portaled to document.body to escape .bcard's overflow:hidden (which clipped the
|
|
851
845
|
dropdown). Inline top/left position it under the kebab; here just a high
|
|
852
846
|
stacking order (below toast/drawer) + word-wrapping items so nothing overflows. */
|
|
@@ -745,7 +745,7 @@ export default function App() {
|
|
|
745
745
|
|
|
746
746
|
// Logged in: unified tabs + cards grid on the left, full-height webview
|
|
747
747
|
// drawer on the right.
|
|
748
|
-
// The Docker-版 cicy-code on :
|
|
748
|
+
// The Docker-版 cicy-code on :8009 has its own dedicated <DockerCard> (right of
|
|
749
749
|
// the local card), so pull it out of the generic node list — else it'd ALSO
|
|
750
750
|
// render as a 自定义 card (the bootstrap registers it as a team for the
|
|
751
751
|
// token-injected 打开/刷新 flow).
|
|
@@ -833,37 +833,11 @@ export default function App() {
|
|
|
833
833
|
)}
|
|
834
834
|
|
|
835
835
|
<div className="app__grid">
|
|
836
|
-
{
|
|
837
|
-
below, NOT also as a LocalTeamCard here. Since :8008 is now BOTH the local
|
|
838
|
-
sidecar port AND the docker app port, it matched localList + dockerTeam and
|
|
839
|
-
rendered twice (the duplicate card). Exclude it from this map. */}
|
|
840
|
-
{showLocal && localList.filter((t) => !isDockerApp(t.base_url)).map((t) => (
|
|
836
|
+
{showLocal && localList.map((t) => (
|
|
841
837
|
<LocalTeamCard key={"local:" + t.id} team={t} onOpen={() => openLocalTeam(t.id)} onRename={renameLocalTeam} onRefresh={fetchLocalTeams} />
|
|
842
838
|
))}
|
|
843
|
-
{/*
|
|
844
|
-
|
|
845
|
-
lands, hold its spot so the 本地 tab is never blank. The slow
|
|
846
|
-
localTeams poll swaps this for the real card automatically. */}
|
|
847
|
-
{showLocal && localList.length === 0 && (
|
|
848
|
-
<div data-id="LocalTeamPlaceholder" className="bcard bcard--local">
|
|
849
|
-
<div className="bcard__accent" />
|
|
850
|
-
<div className="bcard__top">
|
|
851
|
-
<div className="bcard__pill">
|
|
852
|
-
<span className="bcard__dot" data-tone="warn" />
|
|
853
|
-
<LaptopIcon />
|
|
854
|
-
</div>
|
|
855
|
-
</div>
|
|
856
|
-
<div className="bcard__body">
|
|
857
|
-
<h3 className="bcard__name">本地团队</h3>
|
|
858
|
-
<div className="bcard__host">http://127.0.0.1:8008</div>
|
|
859
|
-
<div className="bcard__meta" />
|
|
860
|
-
</div>
|
|
861
|
-
<button type="button" className="bcard__cta" disabled>
|
|
862
|
-
<Spinner />
|
|
863
|
-
<span>{localTeamsFetched ? "正在启动,就绪后自动加入…" : "检测中…"}</span>
|
|
864
|
-
</button>
|
|
865
|
-
</div>
|
|
866
|
-
)}
|
|
839
|
+
{/* 主人: native :8008 退役 —— 不再有"本地团队 正在启动"占位卡(native 已删,
|
|
840
|
+
:8008 永远不会起来,占位会一直转)。cicy-code 用下面的 Docker 卡(:8009)。 */}
|
|
867
841
|
{showLocal && (
|
|
868
842
|
<DockerCard
|
|
869
843
|
dockerTeam={dockerTeam}
|
|
@@ -873,7 +847,7 @@ export default function App() {
|
|
|
873
847
|
// (主人: 必须拿到 token 才能打开,否则被卡在登录页).
|
|
874
848
|
try {
|
|
875
849
|
const r = await window.cicy?.docker?.appOpen?.();
|
|
876
|
-
if (!r?.ok)
|
|
850
|
+
if (!r?.ok) window.alert("拿不到容器 token,无法打开 :8009。请确认服务已就绪(或用卡片菜单「重启」)后再试。");
|
|
877
851
|
} catch (e) { console.warn("[DockerCard] open", e); }
|
|
878
852
|
}}
|
|
879
853
|
cloudTitle={dockerCloudTeam?.title}
|
|
@@ -1695,7 +1669,7 @@ function DockerInstallDrawerHost() {
|
|
|
1695
1669
|
</span>
|
|
1696
1670
|
<div>
|
|
1697
1671
|
<div className="drawer__h">{tr("docker.setupTitle", "安装 Docker cicy-code")}</div>
|
|
1698
|
-
<div className="drawer__sub">127.0.0.1:
|
|
1672
|
+
<div className="drawer__sub">127.0.0.1:8009</div>
|
|
1699
1673
|
</div>
|
|
1700
1674
|
</div>
|
|
1701
1675
|
<div className="drawer__headbtns">
|
|
@@ -1774,7 +1748,7 @@ function DockerInstallDrawerHost() {
|
|
|
1774
1748
|
}
|
|
1775
1749
|
|
|
1776
1750
|
// Docker-版 cicy-code card (Windows only): a SECOND cicy-code instance running
|
|
1777
|
-
// in Docker on :
|
|
1751
|
+
// in Docker on :8009, alongside the native local daemon (:8008). If Docker
|
|
1778
1752
|
// Desktop is missing, the install flow downloads its installer to the user's
|
|
1779
1753
|
// Desktop and runs it (主人指令), streaming progress through the drawer above.
|
|
1780
1754
|
function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
@@ -1786,8 +1760,8 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
1786
1760
|
const [editing, setEditing] = useState(false);
|
|
1787
1761
|
const [draft, setDraft] = useState("");
|
|
1788
1762
|
// 标题 = 云端 team 的 title(和其它团队同一套;refreshCloudTeams 周期刷新 → 自动跟随
|
|
1789
|
-
// 云端改名)。还没建好云端 team 时回退 "
|
|
1790
|
-
const displayName = cloudTitle || "
|
|
1763
|
+
// 云端改名)。还没建好云端 team 时回退 "Docker 团队"。
|
|
1764
|
+
const displayName = cloudTitle || "Docker 团队";
|
|
1791
1765
|
const startEdit = (e) => { e?.stopPropagation?.(); setDraft(displayName); setEditing(true); };
|
|
1792
1766
|
const commitName = async () => {
|
|
1793
1767
|
setEditing(false);
|
|
@@ -1921,7 +1895,7 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
1921
1895
|
if (platform !== "win32" && platform !== "darwin") return null;
|
|
1922
1896
|
|
|
1923
1897
|
// Distinct states (主人: 状态分清楚):
|
|
1924
|
-
// running — :
|
|
1898
|
+
// running — :8009 container healthy → 打开
|
|
1925
1899
|
// dockerRunning — engine up, no container → 启动 (build/start container)
|
|
1926
1900
|
// installed — Docker on disk but engine down → 启动 Docker
|
|
1927
1901
|
// else — not installed → 下载安装
|
|
@@ -1963,10 +1937,10 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
1963
1937
|
const onCta = async () => {
|
|
1964
1938
|
if (isBusy) return;
|
|
1965
1939
|
if (running) {
|
|
1966
|
-
// 主人令:打开很慢 → 先探这个 :
|
|
1940
|
+
// 主人令:打开很慢 → 先探这个 :8009 tab 开过没。开过(openedWc 里有它的
|
|
1967
1941
|
// webContentsId)就**直接 active 秒切**,不再拿 token / 注册 team(那是慢的根)。
|
|
1968
1942
|
try {
|
|
1969
|
-
const r = await window.cicy?.tabs?.activateIfOpen?.("http://127.0.0.1:
|
|
1943
|
+
const r = await window.cicy?.tabs?.activateIfOpen?.("http://127.0.0.1:8009");
|
|
1970
1944
|
if (r?.active) return;
|
|
1971
1945
|
} catch {}
|
|
1972
1946
|
// 没开过 → 显示 loading(打开中…),走慢路径:拿容器 token 再开 tab。
|
|
@@ -2021,32 +1995,29 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
2021
1995
|
ref={menuRef}
|
|
2022
1996
|
style={{ position: "fixed", top: menuPos.top, left: menuPos.left, width: MENU_W }}
|
|
2023
1997
|
onClick={(e) => e.stopPropagation()}>
|
|
2024
|
-
{/*
|
|
1998
|
+
{/* cicy-code 操作(容器内的 cicy-code:更新 / 重启 / 停止)*/}
|
|
2025
1999
|
<button type="button" data-id="DockerCard-update" className="bcard__menu-item is-accent" onClick={runUpdate}>
|
|
2026
2000
|
{tr("docker.update", "更新 cicy-code")}
|
|
2027
2001
|
</button>
|
|
2002
|
+
<button type="button" data-id="DockerCard-restart" className="bcard__menu-item"
|
|
2003
|
+
onClick={() => runOp("restart", () => window.cicy.docker.appRestart(), tr("docker.restarted", "已重启 cicy-code"))}>
|
|
2004
|
+
{tr("docker.restart", "重启 cicy-code")}
|
|
2005
|
+
</button>
|
|
2006
|
+
<button type="button" data-id="DockerCard-stop" className="bcard__menu-item is-danger"
|
|
2007
|
+
onClick={() => runOp("stop", () => window.cicy.docker.appStop(), tr("docker.stopped", "已停止 cicy-code"))}>
|
|
2008
|
+
{tr("docker.stop", "停止 cicy-code")}
|
|
2009
|
+
</button>
|
|
2010
|
+
{/* 常用 */}
|
|
2028
2011
|
<button type="button" data-id="DockerCard-reload" className="bcard__menu-item"
|
|
2029
|
-
onClick={(e) => { e.stopPropagation(); setMenuOpen(false); window.cicy?.tabs?.reloadIfOpen?.("http://127.0.0.1:
|
|
2012
|
+
onClick={(e) => { e.stopPropagation(); setMenuOpen(false); window.cicy?.tabs?.reloadIfOpen?.("http://127.0.0.1:8009", "Docker 团队"); }}>
|
|
2030
2013
|
{tr("docker.reloadWindow", "刷新窗口")}
|
|
2031
2014
|
</button>
|
|
2032
2015
|
<button type="button" data-id="DockerCard-billing" className="bcard__menu-item"
|
|
2033
2016
|
onClick={() => { setMenuOpen(false); openCloudPage(dockerTeam?.cloud_team_id ? `?team=${encodeURIComponent(dockerTeam.cloud_team_id)}` : "?view=usage"); }}>
|
|
2034
2017
|
{tr("docker.billing", "帐单")}
|
|
2035
2018
|
</button>
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
style={{ whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}
|
|
2039
|
-
onClick={(e) => { e.stopPropagation(); setMenuOpen(false); try { navigator.clipboard.writeText("http://127.0.0.1:8008"); } catch {} }}>
|
|
2040
|
-
http://127.0.0.1:8008
|
|
2041
|
-
</button>
|
|
2042
|
-
{/* ── Docker 容器操作(归到一起,divider 分隔)── */}
|
|
2043
|
-
<div className="bcard__menu-sep" data-id="DockerCard-menu-sep" role="separator">
|
|
2044
|
-
<span>{tr("docker.opsGroup", "Docker 容器")}</span>
|
|
2045
|
-
</div>
|
|
2046
|
-
<button type="button" data-id="DockerCard-restart" className="bcard__menu-item"
|
|
2047
|
-
onClick={() => runOp("restart", () => window.cicy.docker.appRestart(), tr("docker.restarted", "已重启 cicy-code"))}>
|
|
2048
|
-
{tr("docker.restart", "重启 cicy-code")}
|
|
2049
|
-
</button>
|
|
2019
|
+
{/* 分隔线:下面是操作整个 Docker 容器的(重启 Docker / 重建 Docker)*/}
|
|
2020
|
+
<div className="bcard__menu-sep" data-id="DockerCard-menu-sep" role="separator" aria-hidden />
|
|
2050
2021
|
<button type="button" data-id="DockerCard-docker-restart" className="bcard__menu-item"
|
|
2051
2022
|
onClick={() => runOp("restart", () => window.cicy.docker.appDockerRestart(), tr("docker.dockerRestarted", "已重启 Docker 容器"))}>
|
|
2052
2023
|
{tr("docker.dockerRestart", "重启 Docker")}
|
|
@@ -2055,10 +2026,6 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
2055
2026
|
onClick={(e) => { e.stopPropagation(); setMenuOpen(false); setConfirmRecreate(true); }}>
|
|
2056
2027
|
{tr("docker.recreate", "重建 Docker")}
|
|
2057
2028
|
</button>
|
|
2058
|
-
<button type="button" data-id="DockerCard-stop" className="bcard__menu-item is-danger"
|
|
2059
|
-
onClick={() => runOp("stop", () => window.cicy.docker.appStop(), tr("docker.stop", "停止 cicy-code"))}>
|
|
2060
|
-
{tr("docker.stop", "停止")}
|
|
2061
|
-
</button>
|
|
2062
2029
|
</div>,
|
|
2063
2030
|
document.body
|
|
2064
2031
|
)}
|
|
@@ -2066,7 +2033,7 @@ function DockerCard({ dockerTeam, cloudTitle, onOpen, onRename, onRefresh }) {
|
|
|
2066
2033
|
)}
|
|
2067
2034
|
</div>
|
|
2068
2035
|
<div className="bcard__body">
|
|
2069
|
-
{/*
|
|
2036
|
+
{/* 8009 现在有独立云端 team(cloud_team_id 是它自己的,不再和 8008 串),所以
|
|
2070
2037
|
标题可改名:本地节点名 + 云端 PATCH 双写(onRename 在父组件处理)。 */}
|
|
2071
2038
|
<div style={{ height: 28, display: "flex", alignItems: "center" }}>
|
|
2072
2039
|
{editing ? (
|
|
@@ -2541,13 +2508,13 @@ function isLocalSidecar(baseUrl) {
|
|
|
2541
2508
|
} catch { return false; }
|
|
2542
2509
|
}
|
|
2543
2510
|
|
|
2544
|
-
// The Docker-版 cicy-code instance — localhost:
|
|
2511
|
+
// The Docker-版 cicy-code instance — localhost:8009. Owned by <DockerCard>, so
|
|
2545
2512
|
// it's filtered out of the generic node lists.
|
|
2546
2513
|
function isDockerApp(baseUrl) {
|
|
2547
2514
|
try {
|
|
2548
2515
|
const p = new URL(baseUrl);
|
|
2549
2516
|
const local = p.hostname === "127.0.0.1" || p.hostname === "localhost" || p.hostname === "::1";
|
|
2550
|
-
return local && p.port === "
|
|
2517
|
+
return local && p.port === "8009";
|
|
2551
2518
|
} catch { return false; }
|
|
2552
2519
|
}
|
|
2553
2520
|
|