cicy-desktop 2.1.97 → 2.1.98
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/package.json +1 -1
- package/src/backends/homepage-react/assets/{index-Bs9ihcPL.css → index-Ka9HcyRP.css} +1 -1
- package/src/backends/homepage-react/assets/index-Moep5uf-.js +365 -0
- package/src/backends/homepage-react/index.html +2 -2
- package/src/backends/sidecar-ipc.js +1 -1
- package/src/i18n/locales/en.json +2 -1
- package/src/i18n/locales/fr.json +2 -1
- package/src/i18n/locales/ja.json +2 -1
- package/src/i18n/locales/zh-CN.json +2 -1
- package/src/sidecar/docker.js +20 -8
- package/workers/render/src/App.css +26 -1
- package/workers/render/src/App.jsx +56 -20
- package/src/backends/homepage-react/assets/index-B04YSZUc.js +0 -365
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
<link rel="icon" type="image/svg+xml" href="./favicon.svg" />
|
|
7
7
|
<link rel="icon" type="image/png" sizes="256x256" href="./favicon-256.png" />
|
|
8
8
|
<title>CiCy Desktop</title>
|
|
9
|
-
<script type="module" crossorigin src="./assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
9
|
+
<script type="module" crossorigin src="./assets/index-Moep5uf-.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="./assets/index-Ka9HcyRP.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
|
@@ -145,7 +145,7 @@ function register({ sidecarLogPath } = {}) {
|
|
|
145
145
|
ipcMain.handle("docker:app-bootstrap", async (e) => {
|
|
146
146
|
if (process.platform !== "win32") return { ok: false, error: "Docker cicy-code is Windows-only" };
|
|
147
147
|
try {
|
|
148
|
-
const installDest = path.join(docker.
|
|
148
|
+
const installDest = path.join(docker.downloadsDir(), "Docker Desktop Installer.exe");
|
|
149
149
|
const result = await docker.bootstrap({
|
|
150
150
|
...appOpts(), installDest,
|
|
151
151
|
onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
|
package/src/i18n/locales/en.json
CHANGED
package/src/i18n/locales/fr.json
CHANGED
package/src/i18n/locales/ja.json
CHANGED
package/src/sidecar/docker.js
CHANGED
|
@@ -160,9 +160,10 @@ function headSize(url, hops = 5) {
|
|
|
160
160
|
async function ensureDownloaded(url, dest, mirror, { emit, phase, label, freshOnIncomplete = false } = {}) {
|
|
161
161
|
const expected = (await headSize(url)) || (mirror ? await headSize(mirror) : 0);
|
|
162
162
|
let have = 0; try { have = fs.statSync(dest).size; } catch {}
|
|
163
|
-
// Complete file already on disk → skip (主人: 完整的 exe
|
|
163
|
+
// Complete file already on disk → skip (主人: 完整的 exe/镜像包就别重下了;用户
|
|
164
|
+
// 自己下到 ~/Downloads 同名文件也走这条直接复用).
|
|
164
165
|
if (expected > 0 && have === expected) {
|
|
165
|
-
emit && emit({ phase, status: "skip", message: `${label}:已下载,跳过`, progress: 100 });
|
|
166
|
+
emit && emit({ phase, status: "skip", message: `${label}:已下载,跳过`, progress: 100, received: have, total: expected, url, dest });
|
|
166
167
|
return dest;
|
|
167
168
|
}
|
|
168
169
|
// A partial left by a PREVIOUS, interrupted/restarted session can be corrupt;
|
|
@@ -187,8 +188,9 @@ async function ensureDownloaded(url, dest, mirror, { emit, phase, label, freshOn
|
|
|
187
188
|
const pct = total ? Math.round((received / total) * 100) : 0;
|
|
188
189
|
if (pct === lastPct) return;
|
|
189
190
|
lastPct = pct;
|
|
190
|
-
// `url`
|
|
191
|
-
|
|
191
|
+
// `url` = source, `dest` = local target path (主人: UI 显示下载目录; lets the
|
|
192
|
+
// user drop a manual download at the same path and have it reused).
|
|
193
|
+
emit && emit({ phase, status: "running", message: label, progress: pct, received, total, url: src, dest });
|
|
192
194
|
},
|
|
193
195
|
});
|
|
194
196
|
if (expected > 0) {
|
|
@@ -244,10 +246,16 @@ function probeHealth(port = 8008, timeoutMs = 2500) {
|
|
|
244
246
|
// ~/Downloads — visible, like the Docker installer on the Desktop). STABLE name
|
|
245
247
|
// (no pid) so a re-run reuses an existing partial/complete file (resume-friendly
|
|
246
248
|
// on a flaky network).
|
|
247
|
-
|
|
249
|
+
// Both the Docker installer AND the image tarball download here (主人: 都下到
|
|
250
|
+
// ~/Downloads). If the user manually downloads either file to this folder with
|
|
251
|
+
// the SAME name, ensureDownloaded sees a complete file and skips the download.
|
|
252
|
+
function downloadsDir() {
|
|
248
253
|
const dir = path.join(process.env["USERPROFILE"] || os.homedir(), "Downloads");
|
|
249
254
|
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
250
|
-
return
|
|
255
|
+
return dir;
|
|
256
|
+
}
|
|
257
|
+
function imageTarballPath() {
|
|
258
|
+
return path.join(downloadsDir(), "cicy-code-latest.tar.gz");
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
// Download the R2 base-env image tarball (no docker needed yet). Split out of
|
|
@@ -424,7 +432,11 @@ async function bootstrap({ onProgress, port = 8008, container = CONTAINER, volum
|
|
|
424
432
|
// running + the daemon coming up (主人: 装 Docker 的同时下载 R2 镜像).
|
|
425
433
|
if (needImage) imgDl = downloadImageTarball({ emit }).catch((e) => { emit({ phase: "image", status: "error", message: `镜像下载失败:${e.message}` }); return null; });
|
|
426
434
|
await installDocker({ emit, dest: installDest });
|
|
427
|
-
|
|
435
|
+
// A silent install doesn't auto-launch the daemon — explicitly start Docker
|
|
436
|
+
// Desktop once its exe lands so the user doesn't have to (主人: 安装启动有问题).
|
|
437
|
+
emit({ phase: "install-docker", status: "running", message: "启动 Docker Desktop…" });
|
|
438
|
+
const launched = await waitUntil(() => { if (dockerDesktopExe()) { startDockerDesktop(); return true; } return false; }, { totalMs: 120000, everyMs: 5000 });
|
|
439
|
+
emit({ phase: "install-docker", status: "running", message: launched ? "等待 Docker 引擎就绪(首次启动较慢,如弹授权/重启请确认)…" : "等待 Docker 安装完成…" });
|
|
428
440
|
const up = await waitUntil(dockerOk, { totalMs: 900000, everyMs: 6000 });
|
|
429
441
|
if (!up) {
|
|
430
442
|
emit({ phase: "install-docker", status: "error", message: "Docker 还没就绪——装好后启动 Docker Desktop,再点「重试」即可(已完成的步骤不会重来)" });
|
|
@@ -475,7 +487,7 @@ async function bootstrap({ onProgress, port = 8008, container = CONTAINER, volum
|
|
|
475
487
|
module.exports = {
|
|
476
488
|
start, stop, stopContainer, restart, checkStatus, loadImage, loadImageFromTarball,
|
|
477
489
|
downloadImageTarball, imagePresent, dockerOk, installDocker,
|
|
478
|
-
bootstrap, probeHealth, readContainerToken, dockerDesktopExe, desktopDir,
|
|
490
|
+
bootstrap, probeHealth, readContainerToken, dockerDesktopExe, desktopDir, downloadsDir, imageTarballPath,
|
|
479
491
|
// platform-agnostic download/retry primitives, reused by native.js
|
|
480
492
|
ensureDownloaded, withRetry, waitUntil, run,
|
|
481
493
|
};
|
|
@@ -1000,10 +1000,35 @@ body {
|
|
|
1000
1000
|
}
|
|
1001
1001
|
.dlbar__fill.is-done { background: #4ade80; }
|
|
1002
1002
|
.dlbar__url {
|
|
1003
|
-
margin-top:
|
|
1003
|
+
margin-top: 6px; font-size: 10.5px; color: #6b7686;
|
|
1004
1004
|
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
1005
1005
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
1006
1006
|
}
|
|
1007
|
+
.dlbar__urlk {
|
|
1008
|
+
display: inline-block; min-width: 16px; text-align: center;
|
|
1009
|
+
margin-right: 5px; padding: 0 4px; border-radius: 4px;
|
|
1010
|
+
font-family: inherit; font-size: 9.5px; color: #8fb0f5; background: rgba(91,141,247,.16);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/* Drawer header buttons (minimize ‒ / close ×) */
|
|
1014
|
+
.drawer__headbtns { display: flex; align-items: center; gap: 2px; }
|
|
1015
|
+
|
|
1016
|
+
/* Minimized drawer → floating restore chip (op keeps running) */
|
|
1017
|
+
.drawer-min {
|
|
1018
|
+
position: fixed; right: 20px; bottom: 20px; z-index: 2001;
|
|
1019
|
+
display: inline-flex; align-items: center; gap: 9px;
|
|
1020
|
+
padding: 10px 15px; border-radius: 999px; cursor: pointer;
|
|
1021
|
+
background: #16181d; border: 1px solid rgba(125,135,150,.22);
|
|
1022
|
+
box-shadow: 0 10px 30px rgba(0,0,0,.5);
|
|
1023
|
+
color: var(--text, #e6eaf0); font-size: 12.5px; font-weight: 550;
|
|
1024
|
+
animation: drawer-min-in .18s cubic-bezier(.2,.8,.2,1);
|
|
1025
|
+
}
|
|
1026
|
+
.drawer-min:hover { border-color: rgba(125,135,150,.4); background: #1b1e24; }
|
|
1027
|
+
@keyframes drawer-min-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
|
|
1028
|
+
.drawer-min__spark { display: inline-flex; width: 16px; height: 16px; align-items: center; justify-content: center; font-weight: 700; }
|
|
1029
|
+
.drawer-min--done .drawer-min__spark { color: #4ade80; }
|
|
1030
|
+
.drawer-min--error .drawer-min__spark { color: #f87171; }
|
|
1031
|
+
.drawer-min__label { font-variant-numeric: tabular-nums; }
|
|
1007
1032
|
.drawer__log-empty { color: var(--text-dim, #9da7b3); }
|
|
1008
1033
|
.drawer__line { display: flex; align-items: baseline; gap: 8px; padding: 1px 0; }
|
|
1009
1034
|
.drawer__t { color: #6b7686; flex: none; font-variant-numeric: tabular-nums; }
|
|
@@ -109,11 +109,13 @@ const updateDrawer = {
|
|
|
109
109
|
};
|
|
110
110
|
emitDrawer();
|
|
111
111
|
},
|
|
112
|
+
minimize() { if (drawerState) { drawerState = { ...drawerState, minimized: true }; emitDrawer(); } },
|
|
113
|
+
restore() { if (drawerState) { drawerState = { ...drawerState, minimized: false }; emitDrawer(); } },
|
|
112
114
|
finish({ ok, message } = {}) {
|
|
113
115
|
if (!drawerState) return;
|
|
114
116
|
const status = ok ? "done" : "error";
|
|
115
117
|
const line = { id: ++drawerLogSeq, t: clockHHMMSS(), phase: "done", status, message: message || (ok ? "更新完成" : "更新失败") };
|
|
116
|
-
drawerState = { ...drawerState, status, phase: "done", logs: [...drawerState.logs, line], lastAt: Date.now() };
|
|
118
|
+
drawerState = { ...drawerState, status, phase: "done", minimized: false, logs: [...drawerState.logs, line], lastAt: Date.now() };
|
|
117
119
|
emitDrawer();
|
|
118
120
|
},
|
|
119
121
|
close() { drawerState = null; emitDrawer(); },
|
|
@@ -136,8 +138,16 @@ function UpdateDrawerHost() {
|
|
|
136
138
|
if (!st) return null;
|
|
137
139
|
const running = st.status === "running";
|
|
138
140
|
const phaseIdx = DRAWER_PHASES.findIndex(([k]) => k === st.phase);
|
|
141
|
+
if (st.minimized) {
|
|
142
|
+
return (
|
|
143
|
+
<button type="button" className={`drawer-min drawer-min--${st.status}`} data-id="UpdateDrawer-restore" onClick={() => updateDrawer.restore()}>
|
|
144
|
+
<span className="drawer-min__spark">{running ? <Spinner /> : st.status === "done" ? "✓" : "!"}</span>
|
|
145
|
+
<span className="drawer-min__label">更新 cicy-code{st.toVer ? ` · v${st.toVer}` : ""}</span>
|
|
146
|
+
</button>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
139
149
|
return (
|
|
140
|
-
<div className="drawer-scrim" data-id="UpdateDrawer-scrim" onClick={() =>
|
|
150
|
+
<div className="drawer-scrim" data-id="UpdateDrawer-scrim" onClick={() => running ? updateDrawer.minimize() : updateDrawer.close()}>
|
|
141
151
|
<div className="drawer" data-id="UpdateDrawer" data-status={st.status} onClick={(e) => e.stopPropagation()}>
|
|
142
152
|
<div className="drawer__head">
|
|
143
153
|
<div className="drawer__title">
|
|
@@ -149,7 +159,10 @@ function UpdateDrawerHost() {
|
|
|
149
159
|
<div className="drawer__sub">{st.fromVer ? `v${st.fromVer}` : "当前"} → {st.toVer ? `v${st.toVer}` : "最新版"}</div>
|
|
150
160
|
</div>
|
|
151
161
|
</div>
|
|
152
|
-
<
|
|
162
|
+
<div className="drawer__headbtns">
|
|
163
|
+
<button type="button" className="drawer__x" data-id="UpdateDrawer-min" title="最小化" onClick={() => updateDrawer.minimize()} aria-label="minimize">‒</button>
|
|
164
|
+
<button type="button" className="drawer__x" data-id="UpdateDrawer-close" title="关闭" onClick={() => running ? updateDrawer.minimize() : updateDrawer.close()} aria-label="close">×</button>
|
|
165
|
+
</div>
|
|
153
166
|
</div>
|
|
154
167
|
|
|
155
168
|
<div className="drawer__steps" data-id="UpdateDrawer-steps">
|
|
@@ -1024,9 +1037,14 @@ function Header({ me, welcome, onLogout, mitmTeam }) {
|
|
|
1024
1037
|
const [appVer, setAppVer] = useState("");
|
|
1025
1038
|
const wrap = useRef(null);
|
|
1026
1039
|
// cicy-desktop's own version, shown at the very bottom of this menu (主人).
|
|
1040
|
+
// app.getVersion() returns { desktop, cicyCodeRef, electron, node } — pick the
|
|
1041
|
+
// desktop version string (was rendering as [object Object]).
|
|
1027
1042
|
useEffect(() => {
|
|
1028
1043
|
let alive = true;
|
|
1029
|
-
window.cicy?.app?.getVersion?.().then((v) => {
|
|
1044
|
+
window.cicy?.app?.getVersion?.().then((v) => {
|
|
1045
|
+
if (!alive) return;
|
|
1046
|
+
setAppVer(typeof v === "string" ? v : String(v?.desktop || ""));
|
|
1047
|
+
}).catch(() => {});
|
|
1030
1048
|
return () => { alive = false; };
|
|
1031
1049
|
}, []);
|
|
1032
1050
|
// Click-outside closes the dropdown (mirrors LocalTeamCard's ⋯ menu).
|
|
@@ -1444,28 +1462,30 @@ let dockerDrawerState = null; // null = closed
|
|
|
1444
1462
|
function emitDockerDrawer() { dockerDrawerListeners.forEach((l) => l(dockerDrawerState)); }
|
|
1445
1463
|
const dockerDrawer = {
|
|
1446
1464
|
open({ onRetry } = {}) {
|
|
1447
|
-
dockerDrawerState = { status: "running", phase: "install-docker", logs: [], bars: {}, onRetry: onRetry || null, lastAt: Date.now() };
|
|
1465
|
+
dockerDrawerState = { status: "running", phase: "install-docker", logs: [], bars: {}, minimized: false, onRetry: onRetry || null, lastAt: Date.now() };
|
|
1448
1466
|
emitDockerDrawer();
|
|
1449
1467
|
},
|
|
1468
|
+
minimize() { if (dockerDrawerState) { dockerDrawerState = { ...dockerDrawerState, minimized: true }; emitDockerDrawer(); } },
|
|
1469
|
+
restore() { if (dockerDrawerState) { dockerDrawerState = { ...dockerDrawerState, minimized: false }; emitDockerDrawer(); } },
|
|
1450
1470
|
push(ev = {}) {
|
|
1451
1471
|
if (!dockerDrawerState) return;
|
|
1452
1472
|
const phase = ev.phase === "health" ? "container" : (ev.phase || dockerDrawerState.phase);
|
|
1453
1473
|
const next = { ...dockerDrawerState, phase, lastAt: Date.now() };
|
|
1454
1474
|
const hasPct = Number.isFinite(ev.progress);
|
|
1455
|
-
|
|
1456
|
-
//
|
|
1457
|
-
|
|
1458
|
-
|
|
1475
|
+
const isDl = phase === "install-docker" || phase === "image";
|
|
1476
|
+
// Any download-related event (running %, skip, done — they carry url/dest)
|
|
1477
|
+
// drives a per-phase PROGRESS BAR, not a log line, so the log doesn't
|
|
1478
|
+
// scroll-spam (主人: 下载不要输出滚动/日志太多).
|
|
1479
|
+
if (isDl && (hasPct || ev.dest || ev.url)) {
|
|
1459
1480
|
const prev = dockerDrawerState.bars?.[phase] || {};
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1481
|
+
const progress = hasPct ? ev.progress : (ev.status === "skip" || ev.status === "done") ? 100 : prev.progress;
|
|
1482
|
+
next.bars = { ...dockerDrawerState.bars, [phase]: { progress, received: ev.received ?? prev.received, total: ev.total ?? prev.total, url: ev.url || prev.url, dest: ev.dest || prev.dest } };
|
|
1483
|
+
}
|
|
1484
|
+
// Log only milestone events — never the per-% running download ticks.
|
|
1485
|
+
const isRunningTick = ev.status === "running" && hasPct && isDl;
|
|
1486
|
+
if (!isRunningTick) {
|
|
1463
1487
|
const line = { id: ++dockerDrawerLogSeq, t: clockHHMMSS(), phase, status: ev.status || "running", message: ev.message || "" };
|
|
1464
1488
|
next.logs = [...dockerDrawerState.logs, line];
|
|
1465
|
-
if (ev.url) {
|
|
1466
|
-
const prev = dockerDrawerState.bars?.[phase] || {};
|
|
1467
|
-
next.bars = { ...dockerDrawerState.bars, [phase]: { ...prev, url: ev.url } };
|
|
1468
|
-
}
|
|
1469
1489
|
}
|
|
1470
1490
|
dockerDrawerState = next;
|
|
1471
1491
|
emitDockerDrawer();
|
|
@@ -1474,7 +1494,8 @@ const dockerDrawer = {
|
|
|
1474
1494
|
if (!dockerDrawerState) return;
|
|
1475
1495
|
const status = ok ? "done" : "error";
|
|
1476
1496
|
const line = { id: ++dockerDrawerLogSeq, t: clockHHMMSS(), phase: "done", status, message: message || (ok ? "完成" : "失败") };
|
|
1477
|
-
|
|
1497
|
+
// Pop back open on finish so the user sees the result even if minimized.
|
|
1498
|
+
dockerDrawerState = { ...dockerDrawerState, status, phase: "done", minimized: false, logs: [...dockerDrawerState.logs, line], lastAt: Date.now() };
|
|
1478
1499
|
emitDockerDrawer();
|
|
1479
1500
|
},
|
|
1480
1501
|
close() { dockerDrawerState = null; emitDockerDrawer(); },
|
|
@@ -1501,7 +1522,8 @@ function DownloadBar({ phaseKey, bar }) {
|
|
|
1501
1522
|
<span className="dlbar__pct">{pct}%{bar?.total ? ` · ${fmtBytes(bar.received)} / ${fmtBytes(bar.total)}` : ""}</span>
|
|
1502
1523
|
</div>
|
|
1503
1524
|
<div className="dlbar__track"><div className={`dlbar__fill${done ? " is-done" : ""}`} style={{ width: `${pct}%` }} /></div>
|
|
1504
|
-
{bar?.url && <div className="dlbar__url" title={bar.url}>{bar.url}</div>}
|
|
1525
|
+
{bar?.url && <div className="dlbar__url" title={bar.url}><span className="dlbar__urlk">源</span> {bar.url}</div>}
|
|
1526
|
+
{bar?.dest && <div className="dlbar__url" title={bar.dest}><span className="dlbar__urlk">存</span> {bar.dest}</div>}
|
|
1505
1527
|
</div>
|
|
1506
1528
|
);
|
|
1507
1529
|
}
|
|
@@ -1514,8 +1536,19 @@ function DockerInstallDrawerHost() {
|
|
|
1514
1536
|
const running = st.status === "running";
|
|
1515
1537
|
const phaseIdx = DOCKER_PHASES.findIndex(([k]) => k === st.phase);
|
|
1516
1538
|
const dlBars = ["install-docker", "image"].filter((k) => st.bars?.[k]);
|
|
1539
|
+
// Minimized → a floating restore chip (op keeps running in the background).
|
|
1540
|
+
if (st.minimized) {
|
|
1541
|
+
const pcts = dlBars.map((k) => st.bars[k]?.progress).filter(Number.isFinite);
|
|
1542
|
+
const overall = pcts.length ? Math.round(pcts.reduce((a, b) => a + b, 0) / pcts.length) : null;
|
|
1543
|
+
return (
|
|
1544
|
+
<button type="button" className={`drawer-min drawer-min--${st.status}`} data-id="DockerDrawer-restore" onClick={() => dockerDrawer.restore()}>
|
|
1545
|
+
<span className="drawer-min__spark">{running ? <Spinner /> : st.status === "done" ? "✓" : "!"}</span>
|
|
1546
|
+
<span className="drawer-min__label">{tr("docker.setupTitle", "安装 Docker cicy-code")}{overall != null ? ` · ${overall}%` : ""}</span>
|
|
1547
|
+
</button>
|
|
1548
|
+
);
|
|
1549
|
+
}
|
|
1517
1550
|
return (
|
|
1518
|
-
<div className="drawer-scrim" data-id="DockerDrawer-scrim" onClick={() =>
|
|
1551
|
+
<div className="drawer-scrim" data-id="DockerDrawer-scrim" onClick={() => running ? dockerDrawer.minimize() : dockerDrawer.close()}>
|
|
1519
1552
|
<div className="drawer" data-id="DockerDrawer" data-status={st.status} onClick={(e) => e.stopPropagation()}>
|
|
1520
1553
|
<div className="drawer__head">
|
|
1521
1554
|
<div className="drawer__title">
|
|
@@ -1527,7 +1560,10 @@ function DockerInstallDrawerHost() {
|
|
|
1527
1560
|
<div className="drawer__sub">127.0.0.1:8009</div>
|
|
1528
1561
|
</div>
|
|
1529
1562
|
</div>
|
|
1530
|
-
<
|
|
1563
|
+
<div className="drawer__headbtns">
|
|
1564
|
+
<button type="button" className="drawer__x" data-id="DockerDrawer-min" title={tr("common.minimize", "最小化")} onClick={() => dockerDrawer.minimize()} aria-label="minimize">‒</button>
|
|
1565
|
+
<button type="button" className="drawer__x" data-id="DockerDrawer-close" title={tr("common.close", "关闭")} onClick={() => running ? dockerDrawer.minimize() : dockerDrawer.close()} aria-label="close">×</button>
|
|
1566
|
+
</div>
|
|
1531
1567
|
</div>
|
|
1532
1568
|
|
|
1533
1569
|
<div className="drawer__steps" data-id="DockerDrawer-steps">
|