docs: define portable directory defaults
This commit is contained in:
+5
-1
@@ -17,8 +17,10 @@
|
||||
7. 切换深色主题,并在窄窗口查看导航和表单重排。
|
||||
8. 点击“设置”:检查四个路径的 Label、选择/搜索按钮;Chrome/Edge 的“选择”必须打开 Windows 原生 `.exe` 文件选择器,取消后保留输入。搜索期间按钮显示“取消”,再次点击取消当前搜索;搜索命中当前路径时在同一字段旁显示已确认反馈,切换页面后状态保持。
|
||||
9. 双击“运行中”或“外部已关联”实例,点击“查看标签页”:检查读取中、目标列表、只读边界文案与 Escape/关闭后回到入口。未启动或没有实际端口的实例应禁用入口。
|
||||
10. 设置代理:检查代理名称和代理地址均有 Label;选择项会回填两项,保存修改后实例列表仅显示最新代理名称。新建实例应默认填入程序目录内不同的 `user_data_dirs\\<实例 ID>` 子目录;窄窗口下代理列不能挤压操作按钮。
|
||||
10. 设置代理:检查代理名称和代理地址均有 Label;选择项会回填两项,保存修改后实例列表仅显示最新代理名称。新建实例应默认填入程序目录内不同的 `user_data_dirs\\instance-<安全化名称>-<短ID>` 子目录;窄窗口下代理列不能挤压操作按钮。
|
||||
11. 点击实例删除:默认确认只删除 Chub 配置。对于已退出实例,勾选“同时删除 User Data Dir 中的数据”后确认按钮改为“删除实例及数据”,并显示目录和不可恢复警告;运行或外部占用实例必须禁用该选项。取消、占用或失败时实例与目录保留。
|
||||
12. 设置默认目录:确认默认 User Data Dir 与日志目录显示“程序目录(chub.exe 所在目录)”下的 `user_data_dirs` 与 `logs`,可选择自定义目录或恢复程序目录默认;已有实例目录不改变。
|
||||
13. 新建或编辑实例:名称与现有名称(忽略大小写)重复时,名称字段就地提示且保留输入;默认目录使用 `instance-<安全化名称>-<短ID>`,改名不会重命名已有目录。模拟目录不可写时,保留表单并提示选择可写目录。
|
||||
|
||||
## 当前原型假设
|
||||
|
||||
@@ -30,6 +32,7 @@
|
||||
- 外部实例显示占用证据,但不提供接管或强制关闭入口。
|
||||
- 实例删除默认只移除配置;数据删除是明确、默认未选中的附加选择,且只能对已退出/启动失败实例发起。实际删除前仍复核占用和目录边界;原型只模拟结果,不执行文件删除。
|
||||
- “标签页(CDP 页面目标)”只读取已验证 loopback CDP 端点的展示 DTO;不会显示 WebSocket 调试地址,不提供任何页面或浏览器控制。它不是物理窗口管理视图。
|
||||
- “程序目录”是实际 `chub.exe` 的父目录,不是启动快捷方式的当前工作目录。设置以 portable/custom 模式保存目录来源;portable 在应用启动时重新解析,custom 只在用户显式选择后生效。
|
||||
- 原型中的“确认”只模拟事件反馈,不启动真实浏览器或改变文件。
|
||||
|
||||
## Gio 交接方向
|
||||
@@ -44,5 +47,6 @@
|
||||
| 详情抽屉 | 自定义辅助面板,关闭后焦点返回实例行 |
|
||||
| 确认对话框 | 模态层限制背景交互,Escape 取消,危险操作不默认聚焦 |
|
||||
| 实例数据删除 | 持久的业务勾选、实例 ID/目录快照/请求代次;后台复核后才允许删除并在成功后持久化配置 |
|
||||
| 便携目录与名称 | portable/custom 目录模式、稳定实例 ID 与安全化目录名;后台目录创建/可写预检,名称字段级唯一性错误 |
|
||||
| CDP 页面目标 | 异步只读 DTO、ID/端口/请求快照防过期、Escape 关闭后返回入口焦点 |
|
||||
| Toast/状态区 | 非模态状态反馈;异步结果通过统一事件触发重绘 |
|
||||
|
||||
@@ -283,8 +283,8 @@
|
||||
<div class="field"><label for="edgePath">Edge 可执行文件</label><div class="path-field"><input id="edgePath" value="自动发现:msedge.exe"><button type="button" class="secondary browse-setting" data-target="edgePath">选择文件</button><button type="button" class="secondary search-setting" data-target="edgePath">搜索</button></div><small>Chrome 与 Edge 可分别设置,也可以继续使用自动发现。</small></div>
|
||||
</div>
|
||||
<div class="form-section"><h3>默认目录</h3>
|
||||
<div class="field"><label for="defaultDataDir">默认 user data dir 根目录</label><div class="path-field"><input id="defaultDataDir" value="D:\\Chub\\profiles"><button type="button" class="secondary browse-setting" data-target="defaultDataDir">选择目录</button><button type="button" class="secondary search-setting" data-target="defaultDataDir">搜索</button></div><small>新建实例时作为默认建议路径;不会移动或删除已有 profile。</small></div>
|
||||
<div class="field"><label for="logDir">日志目录</label><div class="path-field"><input id="logDir" value="D:\\Chub\\logs"><button type="button" class="secondary browse-setting" data-target="logDir">选择目录</button><button type="button" class="secondary search-setting" data-target="logDir">搜索</button></div><small>日志仅保存必要诊断信息,并过滤敏感参数。</small></div>
|
||||
<div class="field"><label for="defaultDataDir">默认 User Data Dir 根目录</label><div class="path-field"><input id="defaultDataDir" value="D:\\Tools\\chub\\user_data_dirs"><button type="button" class="secondary browse-setting" data-target="defaultDataDir">选择目录</button><button type="button" class="secondary restore-portable-setting" data-target="defaultDataDir">恢复程序目录默认</button><button type="button" class="secondary search-setting" data-target="defaultDataDir">搜索</button></div><small>默认跟随程序目录(chub.exe 所在目录)的 <span class="mono">user_data_dirs</span>;只影响之后新建的实例,不移动已有 profile。</small></div>
|
||||
<div class="field"><label for="logDir">日志目录</label><div class="path-field"><input id="logDir" value="D:\\Tools\\chub\\logs"><button type="button" class="secondary browse-setting" data-target="logDir">选择目录</button><button type="button" class="secondary restore-portable-setting" data-target="logDir">恢复程序目录默认</button><button type="button" class="secondary search-setting" data-target="logDir">搜索</button></div><small>默认跟随程序目录的 <span class="mono">logs</span>;日志仅保存必要诊断信息,并过滤敏感参数。</small></div>
|
||||
</div>
|
||||
<div class="form-section"><h3>代理</h3><p class="help">仅保存无认证代理。下拉框、实例创建和编辑均展示完整地址。</p><div class="field"><label for="proxySelect">已保存代理</label><select id="proxySelect"><option value="">选择代理(新建)</option></select><small>选择后会回填地址;选择“新建”会清空输入框。</small></div><div class="field"><label for="proxyServer">代理地址</label><div class="path-field"><input id="proxyServer" placeholder="例如:http://127.0.0.1:8080"><button type="button" class="primary" id="proxySave">保存代理</button><button type="button" class="danger-button" id="proxyDelete" disabled>删除代理</button></div><small>支持 http、https、socks4、socks5 的 <span class="mono">scheme://host:port</span>;不允许用户名、密码或路径。</small></div><div class="settings-status" id="proxyStatus">新建代理:填写地址后保存。</div></div>
|
||||
<div class="settings-actions"><button type="button" class="secondary" id="discardSettings">取消</button><button type="submit" class="primary">保存设置</button></div>
|
||||
@@ -376,6 +376,7 @@
|
||||
const x = instances.find(i => i.id === editingID); if (!x) return finishEdit();
|
||||
const name = $('editName').value.trim(), dir = $('editDataDir').value.trim(), url = $('editURL').value.trim(), preferredPort = Number($('editPreferredPort').value);
|
||||
if (!name || !dir) { showToast('请填写实例名称和 User Data Dir。'); (!name ? $('editName') : $('editDataDir')).focus(); return; }
|
||||
if (instances.some(item => item.id !== x.id && item.name.trim().toLocaleLowerCase() === name.toLocaleLowerCase())) { showToast('实例名称已存在,请更换。'); $('editName').focus(); return; }
|
||||
const locked = editIsLocked(x); x.name = name; x.url = url; x.proxyId = $('editProxy').value;
|
||||
if (!locked) { if (!Number.isInteger(preferredPort) || preferredPort < 1024 || preferredPort > 65535) { showToast('请填写有效的首选调试端口。'); $('editPreferredPort').focus(); return; } x.dir = dir; x.kind = document.querySelector('input[name="editKind"]:checked')?.value || x.kind; x.browser = x.kind === 'edge' ? 'Edge' : 'Chrome'; x.preferredPort = preferredPort; }
|
||||
if ($('discardDialog').open) $('discardDialog').close(); renderRows(); const savedName = x.name; finishEdit(); showToast(`已保存“${savedName}”的实例配置。`);
|
||||
@@ -400,7 +401,7 @@
|
||||
$('tabsTargetList').hidden = !targets.length;
|
||||
}, 380);
|
||||
}
|
||||
function openLaunch() { $('drawer').innerHTML = `<div class="drawer-head"><div><h2>新建浏览器实例</h2><p>配置完成后,Chub 会在本机启动一个隔离环境。</p></div><button class="icon-button" id="closeDrawer" aria-label="关闭新建实例">×</button></div><form class="drawer-body instance-form-workspace" id="launchForm"><div class="form-section"><h3>浏览器配置</h3><div class="field"><span>浏览器类型 <span>*</span></span><div class="radio-row" role="radiogroup" aria-label="浏览器类型"><label><input type="radio" name="kind" value="chrome" checked> Chrome</label><label><input type="radio" name="kind" value="edge"> Edge</label></div></div><div class="field"><label for="exe">可执行文件</label><input id="exe" value="自动发现" aria-describedby="exeHelp"><small id="exeHelp">留空时按浏览器类型搜索标准安装路径。</small></div></div><div class="form-section"><h3>运行环境</h3><div class="field"><label for="dataDir">User Data Dir <span>*</span></label><div class="path-field"><input id="dataDir" placeholder="例如:D:\\Chub\\profiles\\new-instance" required><button type="button" class="secondary" id="chooseInstanceDir">选择目录</button></div><small>必须是绝对路径;同一目录不能被多个实例同时使用。</small></div><div class="field"><label for="profile">profile-directory</label><input id="profile" value="Default"></div><div class="field"><label for="url">启动 URL(可选)</label><input id="url" type="url" placeholder="https://example.com"><small>仅支持 http/https;留空时启动浏览器默认页。</small></div><div class="field"><label for="launchPreferredPort">首选调试端口</label><input id="launchPreferredPort" type="number" min="1024" max="65535" value="9670" inputmode="numeric"><small>系统从此值开始建议未被其他 Chub 实例预留的端口;实际端口会在启动时验证。</small></div><div class="field"><label for="launchProxy">代理</label><select id="launchProxy"><option value="">无代理</option></select><small>选择设置中保存的无认证代理;端点会在启动时安全传递。</small></div></div><div class="form-section"><h3>启动选项</h3><label class="check-line"><input type="checkbox" id="headless"> <span><b>无头模式</b><br><small class="help">启动后台浏览器,不显示窗口。</small></span></label><label class="check-line"><input type="checkbox" id="remember" checked> <span><b>保存此配置</b><br><small class="help">只保存路径和非敏感参数。</small></span></label></div><div class="drawer-actions"><button type="button" class="secondary" id="cancelLaunch">取消</button><button type="submit" class="primary">启动实例</button></div></form>`; showDrawer(); renderProxyOptions($('launchProxy')); $('closeDrawer').onclick = hideDrawer; $('cancelLaunch').onclick = hideDrawer; $('chooseInstanceDir').onclick = () => showToast('原型演示:将打开目录选择器。'); $('launchForm').onsubmit = e => { e.preventDefault(); const data = $('dataDir'), port = Number($('launchPreferredPort').value); if (!data.value.trim()) { data.focus(); data.parentElement.classList.add('error'); const msg = document.createElement('div'); msg.className = 'field-error'; msg.textContent = '请填写 User Data Dir。'; data.parentElement.appendChild(msg); return; } if (!Number.isInteger(port) || port < 1024 || port > 65535) { $('launchPreferredPort').focus(); showToast('请填写有效的首选调试端口。'); return; } const proxy = proxyLabel($('launchProxy').value); hideDrawer(); setTimeout(() => showToast(`启动请求已提交(首选端口 ${port};${proxy}),实例进入 STARTING 状态。`), 80); }; }
|
||||
function openLaunch() { $('drawer').innerHTML = `<div class="drawer-head"><div><h2>新建浏览器实例</h2><p>配置完成后,Chub 会在本机启动一个隔离环境。</p></div><button class="icon-button" id="closeDrawer" aria-label="关闭新建实例">×</button></div><form class="drawer-body instance-form-workspace" id="launchForm"><div class="form-section"><h3>浏览器配置</h3><div class="field"><label for="instanceName">实例名称 <span>*</span></label><input id="instanceName" placeholder="例如:审核 Edge" required><small>名称必须唯一;目录会使用安全化名称和稳定 ID,之后改名不会移动已有数据。</small></div><div class="field"><span>浏览器类型 <span>*</span></span><div class="radio-row" role="radiogroup" aria-label="浏览器类型"><label><input type="radio" name="kind" value="chrome" checked> Chrome</label><label><input type="radio" name="kind" value="edge"> Edge</label></div></div><div class="field"><label for="exe">可执行文件</label><input id="exe" value="自动发现" aria-describedby="exeHelp"><small id="exeHelp">留空时按浏览器类型搜索标准安装路径。</small></div></div><div class="form-section"><h3>运行环境</h3><div class="field"><label for="dataDir">User Data Dir <span>*</span></label><div class="path-field"><input id="dataDir" value="D:\\Tools\\chub\\user_data_dirs\\instance-审核-7f3a21" required><button type="button" class="secondary" id="chooseInstanceDir">选择目录</button></div><small>默认跟随程序目录;启动前会验证目录可写,同一目录不能被多个实例同时使用。</small></div><div class="field"><label for="profile">profile-directory</label><input id="profile" value="Default"></div><div class="field"><label for="url">启动 URL(可选)</label><input id="url" type="url" placeholder="https://example.com"><small>仅支持 http/https;留空时启动浏览器默认页。</small></div><div class="field"><label for="launchPreferredPort">首选调试端口</label><input id="launchPreferredPort" type="number" min="1024" max="65535" value="9670" inputmode="numeric"><small>系统从此值开始建议未被其他 Chub 实例预留的端口;实际端口会在启动时验证。</small></div><div class="field"><label for="launchProxy">代理</label><select id="launchProxy"><option value="">无代理</option></select><small>选择设置中保存的无认证代理;端点会在启动时安全传递。</small></div></div><div class="form-section"><h3>启动选项</h3><label class="check-line"><input type="checkbox" id="headless"> <span><b>无头模式</b><br><small class="help">启动后台浏览器,不显示窗口。</small></span></label><label class="check-line"><input type="checkbox" id="remember" checked> <span><b>保存此配置</b><br><small class="help">只保存路径和非敏感参数。</small></span></label></div><div class="drawer-actions"><button type="button" class="secondary" id="cancelLaunch">取消</button><button type="submit" class="primary">启动实例</button></div></form>`; showDrawer(); renderProxyOptions($('launchProxy')); $('closeDrawer').onclick = hideDrawer; $('cancelLaunch').onclick = hideDrawer; $('chooseInstanceDir').onclick = () => showToast('原型演示:将打开目录选择器。'); $('launchForm').onsubmit = e => { e.preventDefault(); const name = $('instanceName'), data = $('dataDir'), port = Number($('launchPreferredPort').value); if (!name.value.trim()) { name.focus(); showToast('请输入实例名称。'); return; } if (instances.some(item => item.name.trim().toLocaleLowerCase() === name.value.trim().toLocaleLowerCase())) { name.focus(); showToast('实例名称已存在,请更换。'); return; } if (!data.value.trim()) { data.focus(); data.parentElement.classList.add('error'); const msg = document.createElement('div'); msg.className = 'field-error'; msg.textContent = '请填写 User Data Dir。'; data.parentElement.appendChild(msg); return; } if (!Number.isInteger(port) || port < 1024 || port > 65535) { $('launchPreferredPort').focus(); showToast('请填写有效的首选调试端口。'); return; } const proxy = proxyLabel($('launchProxy').value); hideDrawer(); setTimeout(() => showToast(`启动请求已提交(首选端口 ${port};${proxy}),实例进入 STARTING 状态。`), 80); }; }
|
||||
function showDrawer() { $('drawer').classList.add('open'); $('backdrop').classList.add('open'); $('drawer').setAttribute('aria-hidden', 'false'); setTimeout(() => $('drawer').querySelector('button, input, select')?.focus(), 60); }
|
||||
function hideDrawer() { $('drawer').classList.remove('open'); $('backdrop').classList.remove('open'); $('drawer').setAttribute('aria-hidden', 'true'); }
|
||||
function openConfirm(id, action) { const x = instances.find(i => i.id === id); modalAction = action; $('dialogTitle').textContent = action === 'restart' ? '重启浏览器实例?' : action === 'start' ? '启动浏览器实例?' : '关闭浏览器实例?'; $('dialogText').textContent = action === 'restart' ? `将先请求“${x.name}”正常退出,再使用相同配置重新启动。` : action === 'start' ? `将使用已保存配置启动“${x.name}”。` : `将请求“${x.name}”正常退出。未保存的网页内容由浏览器自行处理。`; $('dialogWarning').hidden = action !== false; $('dialogConfirm').textContent = action === false ? '优雅关闭' : action === 'restart' ? '确认重启' : '启动实例'; $('dialogConfirm').className = action === false ? 'danger-button' : 'primary'; $('confirmDialog').showModal(); }
|
||||
@@ -454,6 +455,7 @@
|
||||
document.querySelectorAll('.search-setting').forEach(b => b.onclick = () => { if (b.dataset.timer) { clearTimeout(Number(b.dataset.timer)); delete b.dataset.timer; b.textContent = '搜索'; showToast('已取消当前搜索。'); return; } b.textContent = '取消'; showToast('正在搜索可用路径…'); b.dataset.timer = String(setTimeout(() => { delete b.dataset.timer; b.textContent = '搜索'; showToast('搜索完成:已更新当前字段。'); }, 1600)); });
|
||||
$('proxySelect').onchange = () => { selectedProxyID = $('proxySelect').value; renderProxies(); $('proxyServer').focus(); };
|
||||
$('proxySave').onclick = () => { const server = $('proxyServer').value.trim().toLowerCase(); if (!/^(https?|socks[45]):\/\/[^\/@?#:]+(?::\d+)$/.test(server)) { $('proxyStatus').textContent = '请填写无认证的 scheme://host:port 代理地址。'; return; } if (proxies.some(p => p.server === server && p.id !== selectedProxyID)) { $('proxyStatus').textContent = '该代理地址已存在,请从下拉框选择它。'; return; } if (selectedProxyID) { const p = proxies.find(item => item.id === selectedProxyID); p.server = server; p.name = server; } else { selectedProxyID = `proxy-${Date.now()}`; proxies.push({ id: selectedProxyID, name: server, server }); } renderProxies(); showToast('代理已保存;后续启动将使用最新端点。'); };
|
||||
document.querySelectorAll('.restore-portable-setting').forEach(button => button.onclick = () => { const target = $(button.dataset.target); target.value = button.dataset.target === 'logDir' ? 'D:\\Tools\\chub\\logs' : 'D:\\Tools\\chub\\user_data_dirs'; showToast('已恢复为程序目录默认值。'); });
|
||||
$('proxyDelete').onclick = () => { const proxy = proxies.find(p => p.id === selectedProxyID), uses = instances.filter(i => i.proxyId === selectedProxyID).length; if (!proxy) return; if (uses) { $('proxyStatus').textContent = `该代理仍被 ${uses} 个实例使用,不能删除。`; return; } $('proxyDeleteText').textContent = `确定删除代理“${proxy.server}”?此操作不会修改任何实例。`; $('proxyDeleteDialog').showModal(); setTimeout(() => $('proxyDeleteCancel').focus(), 0); };
|
||||
$('proxyDeleteCancel').onclick = () => $('proxyDeleteDialog').close(); $('proxyDeleteDialog').addEventListener('cancel', e => { e.preventDefault(); $('proxyDeleteDialog').close(); }); $('proxyDeleteConfirm').onclick = () => { const proxy = proxies.find(p => p.id === selectedProxyID); if (proxy) proxies.splice(proxies.indexOf(proxy), 1); selectedProxyID = ''; $('proxyDeleteDialog').close(); renderProxies(); $('proxySelect').focus(); showToast('代理已删除。'); };
|
||||
$('settingsForm').addEventListener('submit', e => { e.preventDefault(); $('settingsStatus').textContent = '设置已保存 · 最后更新:刚刚'; $('settingsStatus').classList.remove('warning'); showToast('设置已保存,后续新建实例将使用新的默认值。'); });
|
||||
|
||||
Reference in New Issue
Block a user