feat: release OpsXShell 2.1.0 native Tauri edition
This commit is contained in:
1 parent
e9a0d21758
commit
a3dba0c6ed
311 files changed
+31748
-14897
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# OpsXShell 文档
|
||||
|
||||
文档按使用、开发、验收和发布分开维护,避免把规划能力写成已支持功能。
|
||||
|
||||
| 文档 | 内容 |
|
||||
| ----------------------------------- | -------------------------------------------- |
|
||||
| [使用指南](./user-guide.md) | SSH、SFTP、代理、串口、AI 助手和数据安全 |
|
||||
| [架构说明](./architecture.md) | Tauri/Rust 分层、原生服务、AI 与安全边界 |
|
||||
| [测试验收](./testing-acceptance.md) | 本地门禁、真实 SSH 探针和发版验收 |
|
||||
| [发布指南](./release.md) | macOS/Windows 构建、签名、产物和临时文件清理 |
|
||||
|
||||
项目定位、功能概览和开发命令见仓库根目录的 [README](../README.md),未完成事项只在 [ROADMAP](../ROADMAP.md) 维护。
|
||||
@@ -0,0 +1,148 @@
|
||||
# 架构说明
|
||||
|
||||
## 产品边界
|
||||
|
||||
OpsXShell 是轻量原生的 SSH/SFTP 运维终端,核心范围是连接、终端、文件传输、端口转发、代理、串口和多会话操作。审计、录像、审批和堡垒机能力不在客户端范围内。
|
||||
|
||||
## 目录与职责
|
||||
|
||||
```text
|
||||
src/ React 界面、工作区和 Zustand 状态
|
||||
src/platform/ Tauri command/event 适配层
|
||||
shared/types/ 渲染层 TypeScript 契约
|
||||
src-tauri/src/commands/ 参数边界和统一响应
|
||||
src-tauri/src/domain/ Rust 领域模型
|
||||
src-tauri/src/services/ SSH、SFTP、隧道、PTY、串口、AI、存储和安全策略
|
||||
```
|
||||
|
||||
React 不直接访问 SSH、文件系统或加密实现。Rust 服务持有连接、Shell、SFTP 和串口资源,渲染层只保存资源 ID 与界面状态。新增原生接口时必须同步更新:
|
||||
|
||||
1. Rust command 和 `src-tauri/src/lib.rs` 注册
|
||||
2. `src/desktop.d.ts` 类型契约
|
||||
3. `src/platform/desktop.ts` 适配实现
|
||||
4. `test/setup.ts` 测试替身
|
||||
|
||||
Rust SSH 服务保留 `ssh.rs` 作为连接与 Shell 门面,已将独立边界拆出:
|
||||
|
||||
```text
|
||||
services/
|
||||
├── ssh.rs 连接池、Shell 与稳定公共 API
|
||||
├── ssh/
|
||||
│ ├── auth.rs 密码、私钥、Agent 与默认密钥认证
|
||||
│ ├── known_hosts.rs OpenSSH 主机密钥策略
|
||||
│ ├── proxy.rs HTTP/SOCKS5 连接代理与 ProxyJump
|
||||
│ ├── path.rs 本机路径展开
|
||||
│ └── sftp.rs 远程文件系统与断点传输
|
||||
├── tunnel.rs TunnelManager 与启动/停止编排
|
||||
└── tunnel/
|
||||
├── model.rs 运行实例、计数器和取消令牌
|
||||
├── socks5.rs SOCKS5 服务端协议
|
||||
└── relay.rs TCP/libssh2 双向流转发
|
||||
```
|
||||
|
||||
模块化只约束内部职责,不引入插件框架,也不把 libssh2 资源暴露到 command 层。后续 connection/shell 拆分继续保持现有 Tauri command 契约不变。
|
||||
|
||||
## 原生服务
|
||||
|
||||
| 能力 | 实现 |
|
||||
| -------------------- | ------------------------------------------------ |
|
||||
| SSH/SFTP | `ssh2 0.9` / libssh2 |
|
||||
| HTTP/SOCKS5 连接代理 | Rust TCP 握手后交给 libssh2 |
|
||||
| ProxyJump | libssh2 direct-tcpip + loopback relay,最多 8 跳 |
|
||||
| 本地/远程/动态转发 | libssh2 channel + Rust TCP relay |
|
||||
| 本地终端 | `portable-pty` |
|
||||
| 串口 | `serialport` |
|
||||
| 会话与设置 | 应用数据目录内原子写入 JSON |
|
||||
| 凭据 | AES-256-GCM + `~/.opsxshsell/master.key` |
|
||||
| 主机信任 | OpenSSH `~/.ssh/known_hosts` |
|
||||
| 终端 AI | Rust `reqwest` + OpenAI/Anthropic 兼容协议 |
|
||||
|
||||
## 终端 AI 边界
|
||||
|
||||
AI 是可选的终端助手。XTerm 只把当前焦点分屏的选区或用户明确勾选的当前屏幕交给 Rust;Rust 先脱敏常见密码、Token、Authorization Header、AWS Key 和私钥,再调用 Provider。需要实际处理时采用受控的单工具循环,不允许模型文本直接触发执行。
|
||||
|
||||
```text
|
||||
XTerm 当前焦点分屏
|
||||
-> 选区优先 / 显式当前屏幕
|
||||
-> Rust 本地脱敏
|
||||
-> AiService (OpenAI Chat / Responses / Anthropic)
|
||||
-> 建议文本
|
||||
-> 复制,或单行命令填入 XTerm(不发送回车)
|
||||
-> 执行任务:唯一单行命令 + 用户确认
|
||||
-> 当前 connectionId 的 ssh_exec
|
||||
-> 真实输出脱敏后交给 AI 总结
|
||||
```
|
||||
|
||||
- Provider 配置和模型路由保存在 `ai-providers-v1`,不包含 API Key。
|
||||
- API Key 使用 `ai-provider:<provider-id>:api-key` 作用域写入现有 AES-256-GCM secrets。
|
||||
- 模型可覆盖 Provider 协议和 Endpoint,以支持同一个上游模型的按量和 Plan 路由。
|
||||
- models.dev 目录仅由 Rust 访问三个固定 HTTPS 地址,最多读取 24 MiB;它用于维护和扩展预设,不是应用启动依赖。
|
||||
- new-api、One API 和其他聚合网关通过 OpenAI Compatible Provider 接入。
|
||||
- AI 对话、提示词、终端上下文和回答只存在于当前面板内存中;关闭面板即清空,不写入应用设置。
|
||||
- 新 Provider 的测试命令接收临时 API Key 并发出最小真实完成请求;测试过程不调用凭据存储。只有当前连接字段快照测试成功,界面才允许保存。
|
||||
- 模型返回内容按不可信文本处理。普通模式只有唯一单行代码块可“填入终端”,多行脚本只能复制。
|
||||
- 执行模式只接受唯一单行命令,目标固定为当前活跃 SSH connection;每次调用 `ssh_exec` 前显示目标、命令和风险确认。执行结果只保留在当前面板。
|
||||
- 不创建独立 Kubernetes 客户端或 podexec。集群运维命令沿当前 SSH 会话执行,避免引入本机 kubeconfig 与第二套目标/凭据边界。
|
||||
|
||||
## 端口转发生命周期
|
||||
|
||||
转发规则和运行实例是两种不同状态:
|
||||
|
||||
- `ForwardRule` 由 React 管理并按保存会话 ID 写入设置,只保存类型、端口和目标。
|
||||
- `ForwardStatus` 由 Rust `TunnelManager` 管理,只在当前进程有效,包含监听端口、开始时间、连接数和流量。
|
||||
- 每个运行实例有唯一 `forwardId` 和取消标志。停止、删除正在运行的规则或断开 SSH 连接时,Rust 必须关闭真实 Listener。
|
||||
- 规则支持编辑和复制。复制只产生新的规则 ID,不复制运行态;运行中的规则必须先停止真实 Listener 才能编辑。
|
||||
- 应用重启后不得根据上次 UI 状态显示“运行中”,必须以 `ssh_forward_list` 返回的原生状态为准。
|
||||
|
||||
当前动态 SOCKS5 默认仅绑定 `127.0.0.1`,不对局域网暴露。客户端仅支持 SOCKS5 CONNECT 和无认证方式。
|
||||
|
||||
## SFTP 写入策略
|
||||
|
||||
渲染层通过 `src/platform/sftp.ts` 固定路径方向:
|
||||
|
||||
- `localPath` 永远表示本机文件系统路径,`remotePath` 永远表示 SFTP 服务器路径。
|
||||
- `uploadRemoteFile` 固定为本地源到远程目标;`downloadRemoteFile` 固定为远程源到本地目标。
|
||||
- `readRemoteTextFile`/`writeRemoteTextFile` 只操作远程编辑器内存内容,不等同于下载/上传。
|
||||
- SFTP ID 只能由已连接的 SSH connection 创建,连接失效后不得复用旧 SFTP ID。
|
||||
|
||||
上传和下载使用 `.opsx-part` 临时文件:
|
||||
|
||||
1. 检查临时文件已有长度。
|
||||
2. 从相同偏移继续读取和写入。
|
||||
3. 完成后校验源与目标字节数。
|
||||
4. 校验通过后替换正式目标。
|
||||
|
||||
SSH 传输本身受协议 MAC 保护;当前应用额外执行大小校验,但尚未执行文件内容哈希。
|
||||
|
||||
## 终端输入输出管线
|
||||
|
||||
`TerminalPipeline` 是 XTerm 和 Shell API 之间的轻量边界,单终端与分屏共用:
|
||||
|
||||
- 输入:活动状态更新、Shell 路由、同步输入以及异步写入错误收敛。
|
||||
- 输出:IPC 数据归一化、流式 UTF-8 解码、关键字变换、每帧限量写入和积压上限。
|
||||
- 终端视图只处理布局、焦点与生命周期;Zmodem、OSC 和登录脚本后续作为显式管线阶段增加,不继续堆进工作区组件。
|
||||
|
||||
## ssh:// 深链接
|
||||
|
||||
系统协议由 Tauri deep-link 插件注册,single-instance 插件负责把后续启动参数交回现有进程。原生层只接受 `ssh://`,拒绝 URL 中的密码、路径、查询参数、片段、控制字符和异常用户名。
|
||||
|
||||
深链接不会静默连接任意服务器:Rust 将解析后的 `{ username, host, port }` 放入有界内存队列并通知前端;前端原子取走队列,打开预填充的快速连接窗口供用户确认。冷启动时通知可能早于 WebView,队列仍会保留;运行中唤起则聚焦现有主窗口。
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 敏感凭据必须在 Rust 层加密后落盘。
|
||||
- 不持久化终端命令历史或批量命令输出。
|
||||
- 不持久化 AI 提示词、回答或终端上下文;API Key 不得返回 WebView 或写入普通 settings。
|
||||
- 未实现能力不能在界面中显示为可用。
|
||||
- 自动更新必须具备签名公钥、HTTPS 更新源和回滚策略,否则保持关闭。
|
||||
- 应用不能静默绕过 changed host key 或 known_hosts 不匹配。
|
||||
|
||||
## 体积预算
|
||||
|
||||
| 产物 | 上限 |
|
||||
| -------------- | ----------------------------- |
|
||||
| macOS DMG | 25 MB |
|
||||
| Windows 安装包 | 15 MB,不含 WebView2 引导程序 |
|
||||
| Linux AppImage | 35 MB |
|
||||
|
||||
Release 使用 LTO、单 codegen unit、`panic = abort` 和符号剥离。平台功能必须在对应系统实机验收。
|
||||
@@ -0,0 +1,88 @@
|
||||
# 发布指南
|
||||
|
||||
## 构建命令
|
||||
|
||||
```bash
|
||||
# 当前平台完整安装包
|
||||
npm run build:tauri
|
||||
|
||||
# 仅 macOS App
|
||||
npx tauri build --bundles app
|
||||
|
||||
# macOS arm64
|
||||
npm run dist:mac
|
||||
|
||||
# Windows x86_64(必须在 Windows 或配置完整交叉编译环境)
|
||||
npm run dist:win
|
||||
```
|
||||
|
||||
macOS App 位于:
|
||||
|
||||
```text
|
||||
src-tauri/target/release/bundle/macos/OpsXShell.app
|
||||
```
|
||||
|
||||
指定 `aarch64-apple-darwin` target 时,产物位于对应 target 的 `release/bundle` 下。
|
||||
|
||||
## 临时 DMG
|
||||
|
||||
Tauri 调用 `hdiutil` 制作 DMG 时,可能在异常中断后留下:
|
||||
|
||||
```text
|
||||
rw.<进程号>.OpsXShell_<版本>_aarch64.dmg
|
||||
```
|
||||
|
||||
这些 `rw.*.dmg` 是可写临时镜像,不是发布包。确认没有正在运行的 Tauri/hdiutil 打包任务后可以删除。正式包没有 `rw.<数字>.` 前缀。
|
||||
|
||||
如果 DMG 脚本报错 `resource busy`,先检查是否正从临时挂载目录运行 OpsXShell:
|
||||
|
||||
```bash
|
||||
hdiutil info
|
||||
lsof +D /Volumes/<临时卷名>
|
||||
```
|
||||
|
||||
先正常退出该 OpsXShell 进程,再卸载对应的临时卷并重新构建。不要在应用仍从卷内运行时直接删除 `rw.*.dmg`。
|
||||
|
||||
## 签名和公证
|
||||
|
||||
标签 `tauri-v*` 会触发 `.github/workflows/tauri-release.yml`。正式标签构建要求签名变量齐全。
|
||||
|
||||
macOS:
|
||||
|
||||
- `APPLE_CERTIFICATE`
|
||||
- `APPLE_CERTIFICATE_PASSWORD`
|
||||
- `APPLE_SIGNING_IDENTITY`
|
||||
- `APPLE_ID`
|
||||
- `APPLE_PASSWORD`
|
||||
- `APPLE_TEAM_ID`
|
||||
|
||||
Windows:
|
||||
|
||||
- `WINDOWS_CERTIFICATE_BASE64`
|
||||
- `WINDOWS_CERTIFICATE_PASSWORD`
|
||||
|
||||
缺少凭据时只能生成开发构建,不能标记为正式签名或已公证版本。
|
||||
|
||||
## 产物验证
|
||||
|
||||
```bash
|
||||
npm run release:preflight:mac
|
||||
npm run release:verify:mac
|
||||
|
||||
npm run release:preflight:win
|
||||
npm run release:verify:win
|
||||
```
|
||||
|
||||
验证脚本检查架构、体积和安装包存在性。正式标签还会验证 macOS 签名/Gatekeeper 或 Windows Authenticode 签名。
|
||||
|
||||
安装包还必须包含 `ssh://` 协议注册。macOS 检查 `CFBundleURLTypes`,Windows 安装后检查当前用户 `Software\\Classes\\ssh` 并实际执行一次协议唤起。只验证配置文件不足以证明单实例事件交付正常。
|
||||
|
||||
## 发布前检查
|
||||
|
||||
1. `package.json`、`package-lock.json`、`src-tauri/Cargo.toml`、`Cargo.lock` 和 `tauri.conf.json` 版本一致。
|
||||
2. `npm run acceptance:full` 通过。
|
||||
3. 目标平台构建和签名通过。
|
||||
4. 安装后完成输入框复制/粘贴、SFTP 双向拖拽、普通区域右键菜单、SSH/SFTP/代理/跳板和 `ssh://` 单实例唤起验收。
|
||||
5. 大模型新配置必须先测试后保存;AI“执行任务”必须显示当前 SSH 目标并在确认后只执行一次。
|
||||
5. 检查安装包尺寸,没有上传 `rw.*.dmg`、日志、测试密钥或用户数据。
|
||||
6. 保留上一稳定版本及回滚说明。
|
||||
@@ -0,0 +1,114 @@
|
||||
# 测试与验收
|
||||
|
||||
## 本地门禁
|
||||
|
||||
日常开发执行:
|
||||
|
||||
```bash
|
||||
npm run acceptance:quick
|
||||
```
|
||||
|
||||
依次检查界面类型、测试 API 契约、全部 Vitest 测试、Rust 格式和 Rust 编译。
|
||||
|
||||
提交或发包前执行:
|
||||
|
||||
```bash
|
||||
npm run acceptance:full
|
||||
```
|
||||
|
||||
完整门禁额外执行 ESLint、Clippy 零警告、Rust 测试、前端生产构建和发布脚本测试。任何步骤失败都会立即返回非零状态。
|
||||
|
||||
## 覆盖重点
|
||||
|
||||
- 应用壳在原生 API 可用和不可用时都能稳定渲染
|
||||
- 会话新建、密码/私钥认证、代理和跳板配置
|
||||
- SSH Config 解析和会话导出真实写盘
|
||||
- 批量打开终端、SFTP 和批量命令
|
||||
- Tauri 事件监听注册/销毁竞态
|
||||
- SSH 连接关闭后的状态回收
|
||||
- HTTP CONNECT、SOCKS5 和断点复制
|
||||
- 转发规则不持久化运行状态,复制产生新 ID;原生隧道可启动、查询、停止并在 SSH 断开时清理
|
||||
- SFTP 本地/远程路径参数方向、原生路径上传、选中文件下载、双栏双向拖拽、目录落点、远程文本读写和错误反馈
|
||||
- 终端分屏、剪贴板、快捷键、分片 UTF-8 解码、高输出背压和异步输入错误
|
||||
- `ssh://` 安全解析、冷启动队列、IPv6 格式和快速连接预填充
|
||||
- AI Provider 的 OpenAI Chat、Responses、Anthropic 响应解析和 HTTP Endpoint 限制
|
||||
- models.dev 多种 JSON 形态归一化、目录源部分失败降级和 new-api `/models` 列表
|
||||
- API Key 只进入 Rust secrets,不出现在 `ai-providers-v1`、日志或 WebView 返回值中
|
||||
- 终端上下文选区优先、当前屏幕显式授权、本地脱敏、单行填入和多行拒绝
|
||||
- 新 Provider 测试前不落盘,真实测试成功后才启用保存;任一连接字段变化会使验证快照失效
|
||||
- AI 执行任务固定当前 SSH connection,确认前不得调用 `ssh_exec`,真实输出回传分析且不进入历史
|
||||
- macOS 原生“编辑”菜单以及所有输入框的复制、剪切、粘贴、全选、撤销;应用快捷键不得覆盖这些系统操作
|
||||
- 普通界面右键不出现 WebView 的 Reload/Inspect Element;输入框仍保留系统编辑菜单,自定义右键不受影响
|
||||
|
||||
测试替身必须完整实现 `DesktopApiContract`。新增或删除桌面接口后,`typecheck:test` 必须能发现未同步的契约。
|
||||
|
||||
## AI 助手验收
|
||||
|
||||
自动验收不得使用真实个人 API Key。可以用本机 mock OpenAI Compatible 服务验证以下边界:
|
||||
|
||||
1. Provider 没有 Key 时返回明确错误;保存 Key 后普通设置 JSON 中搜索不到明文。
|
||||
2. HTTPS 为默认要求,任意公网 HTTP Endpoint 被拒绝;只有显式允许后才能访问可信本机 HTTP 服务。
|
||||
3. OpenAI Chat、OpenAI Responses 和 Anthropic 三种响应都能提取文本,错误响应显示 HTTP 状态和供应商消息。
|
||||
4. `Authorization: Bearer`、密码字段、AWS Key 和私钥在请求发出前被替换,并显示脱敏计数。
|
||||
5. 有选区时即使勾选当前屏幕也只发送选区;无选区且未勾选时只发送用户问题。
|
||||
6. 单行命令填入后终端不得出现 `\r` 或 `\n`;多行代码块不显示填入按钮。
|
||||
7. 关闭再打开 AI 面板,之前的提示词和回答不存在。
|
||||
8. models.dev 三个源全部失败时不影响已保存 Provider;部分成功时仍能生成可导入目录。
|
||||
9. 新增 Provider 后测试前“保存”禁用;测试实际发送“你是什么模型?请简短回答。”,期间 settings 与 secrets 均无写入;只有非空成功回答显示 HTTP 200,随后保存才写入加密 Key。
|
||||
10. 切换 Endpoint、模型、协议或 API Key 后“保存”立即重新禁用。
|
||||
11. “执行任务”只接受唯一单行命令;确认对话框打开前和用户取消后 `ssh_exec` 调用次数必须为 0。
|
||||
12. 确认后只向当前 connectionId 执行一次;真实输出交给模型总结,关闭面板后命令与输出消失。
|
||||
|
||||
## 真实 SSH/SFTP 探针
|
||||
|
||||
真实探针默认标记为 ignored,必须显式提供测试服务器:
|
||||
|
||||
```bash
|
||||
OPSX_TEST_SSH_HOST=example.com \
|
||||
OPSX_TEST_SSH_PORT=22 \
|
||||
OPSX_TEST_SSH_USER=root \
|
||||
cargo test --manifest-path src-tauri/Cargo.toml \
|
||||
real_ssh_exec_and_sftp_probe -- --ignored --nocapture
|
||||
```
|
||||
|
||||
该探针会执行 SSH 命令、创建 SFTP 通道,并在远端 `/tmp` 完成一个临时文件的上传、下载、逐字节比对和清理。只有列目录成功不算 SFTP 基础传输验收通过。
|
||||
|
||||
ProxyJump 探针额外设置 `OPSX_TEST_SSH_JUMP_HOST`、`OPSX_TEST_SSH_JUMP_PORT`;使用 `OPSX_TEST_SSH_JUMP_COUNT=2` 可验证两级链。
|
||||
|
||||
动态 SOCKS5 真实探针使用同一组 SSH 环境变量:
|
||||
|
||||
```bash
|
||||
OPSX_TEST_SSH_HOST=example.com \
|
||||
OPSX_TEST_SSH_PORT=22 \
|
||||
OPSX_TEST_SSH_USER=root \
|
||||
cargo test --manifest-path src-tauri/Cargo.toml \
|
||||
real_dynamic_forward_probe -- --ignored --nocapture
|
||||
```
|
||||
|
||||
它会创建随机本地 SOCKS5 监听,通过 SSH `direct-tcpip` 再连回测试主机,读取 `SSH-` 协议头,最后停止监听并确认运行状态已清理。
|
||||
|
||||
## 端口转发验收
|
||||
|
||||
Rust 单元测试会用随机本地端口验证动态监听器的唯一 ID、运行查询、重复启动拒绝和真实停止。实机验收还必须包含:
|
||||
|
||||
1. 在保存会话上一键启动 `127.0.0.1:1080`,用 `curl --proxy socks5h://127.0.0.1:1080 https://example.com` 验证。
|
||||
2. 确认连接数和流量会变化,复制出的代理地址正确。
|
||||
3. 停止后确认端口无法再连接,不能只检查 UI 颜色。
|
||||
4. 重启应用后规则仍在,但状态必须为未运行。
|
||||
5. 断开 SSH 主连接后,关联的本地、远程和 SOCKS5 监听器全部清理。
|
||||
6. 复制规则后确认新规则 ID 独立且状态为未运行;编辑运行中的规则必须先停止原 Listener。
|
||||
|
||||
## ssh:// 验收
|
||||
|
||||
macOS 安装版先检查 `Info.plist` 包含 `ssh` URL scheme,然后分别验证冷启动和运行中唤起:
|
||||
|
||||
```bash
|
||||
plutil -p OpsXShell.app/Contents/Info.plist | grep -A8 CFBundleURLTypes
|
||||
open -a OpsXShell 'ssh://root@opsbase.cn:55555'
|
||||
```
|
||||
|
||||
必须确认只存在一个 OpsXShell 进程、原窗口被聚焦、快速连接目标为 `root@opsbase.cn:55555`。`ssh://root:password@host` 和包含远程路径的链接必须被拒绝。Windows 需要在安装后的注册表和浏览器唤起场景重复验证,macOS 结果不能代替 Windows 验收。
|
||||
|
||||
## 发版验收
|
||||
|
||||
候选发布必须在目标系统完成:安装、首次启动、窗口操作、SSH、SFTP、代理、跳板、串口枚举、休眠恢复、卸载及重新安装。CI 构建成功不能替代 Windows 或 macOS 实机验收。
|
||||
@@ -1,680 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>OpsXShell - 功能预览与架构设计</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<style>
|
||||
body { font-family: 'Inter', sans-serif; }
|
||||
.terminal-text { font-family: 'Menlo', 'Monaco', 'Courier New', monospace; }
|
||||
.tab-active {
|
||||
background: #1e293b;
|
||||
border-top: 2px solid #22d3ee;
|
||||
}
|
||||
.preview-container {
|
||||
aspect-ratio: 16/10;
|
||||
}
|
||||
.feature-done {
|
||||
border-left: 4px solid #22c55e;
|
||||
}
|
||||
.feature-progress {
|
||||
border-left: 4px solid #f59e0b;
|
||||
}
|
||||
.feature-todo {
|
||||
border-left: 4px solid #6b7280;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-950 min-h-screen p-8">
|
||||
<div class="max-w-7xl mx-auto space-y-8">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="text-center mb-8">
|
||||
<h1 class="text-4xl font-bold text-white mb-3">OpsXShell 功能架构</h1>
|
||||
<p class="text-slate-400 text-lg">跨平台 SSH/SFTP 终端工具 · 专业级远程连接解决方案</p>
|
||||
<div class="flex justify-center gap-4 mt-6">
|
||||
<span class="flex items-center gap-2 text-sm text-green-400">
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
已实现
|
||||
</span>
|
||||
<span class="flex items-center gap-2 text-sm text-yellow-400">
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
进行中
|
||||
</span>
|
||||
<span class="flex items-center gap-2 text-sm text-gray-400">
|
||||
<div class="w-3 h-3 rounded-full bg-gray-500"></div>
|
||||
规划中
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Switcher -->
|
||||
<div class="flex justify-center gap-2 mb-6 flex-wrap">
|
||||
<button onclick="showView('terminal')" id="tab-terminal" class="px-6 py-2 rounded-lg text-sm font-medium transition-colors bg-cyan-600 text-white">
|
||||
<i class="fas fa-terminal mr-2"></i>SSH 终端
|
||||
</button>
|
||||
<button onclick="showView('sftp')" id="tab-sftp" class="px-6 py-2 rounded-lg text-sm font-medium transition-colors bg-slate-800 text-slate-300 hover:bg-slate-700">
|
||||
<i class="fas fa-folder-open mr-2"></i>SFTP 管理
|
||||
</button>
|
||||
<button onclick="showView('portforward')" id="tab-portforward" class="px-6 py-2 rounded-lg text-sm font-medium transition-colors bg-slate-800 text-slate-300 hover:bg-slate-700">
|
||||
<i class="fas fa-network-wired mr-2"></i>端口转发
|
||||
</button>
|
||||
<button onclick="showView('features')" id="tab-features" class="px-6 py-2 rounded-lg text-sm font-medium transition-colors bg-slate-800 text-slate-300 hover:bg-slate-700">
|
||||
<i class="fas fa-list-check mr-2"></i>功能清单
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Terminal View -->
|
||||
<div id="view-terminal" class="preview-container">
|
||||
<div class="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-2xl shadow-cyan-500/10 h-full">
|
||||
<!-- Title Bar -->
|
||||
<div class="bg-gradient-to-r from-slate-800 to-slate-700 px-4 py-3 flex items-center justify-between border-b border-slate-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<i class="fas fa-terminal text-cyan-400"></i>
|
||||
<span class="text-slate-200 font-medium">OpsXShell</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex h-[calc(100%-48px)]">
|
||||
<!-- Sidebar -->
|
||||
<div class="w-60 bg-slate-950 border-r border-slate-800 p-3 flex flex-col">
|
||||
<div class="flex items-center gap-2 mb-4 px-2 py-2 bg-slate-800 rounded-lg border border-slate-700">
|
||||
<i class="fas fa-search text-slate-500 text-xs"></i>
|
||||
<span class="text-slate-500 text-xs">搜索会话...</span>
|
||||
</div>
|
||||
<div class="space-y-1 flex-1">
|
||||
<div class="text-xs text-slate-500 font-semibold px-2 mb-2">生产环境</div>
|
||||
<div class="flex items-center gap-2 px-2 py-2.5 bg-cyan-500/20 rounded-lg border border-cyan-500/30 cursor-pointer">
|
||||
<div class="w-2 h-2 rounded-full bg-green-500"></div>
|
||||
<i class="fas fa-server text-cyan-400 text-xs"></i>
|
||||
<span class="text-slate-200 text-sm font-medium">Web-Prod-01</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-2 py-2 hover:bg-slate-800 rounded-lg cursor-pointer transition-colors">
|
||||
<div class="w-2 h-2 rounded-full bg-green-500"></div>
|
||||
<i class="fas fa-database text-slate-500 text-xs"></i>
|
||||
<span class="text-slate-400 text-sm">DB-Master</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="mt-3 w-full bg-cyan-600 hover:bg-cyan-500 text-white text-xs py-2.5 rounded-lg flex items-center justify-center gap-2 transition-colors">
|
||||
<i class="fas fa-plus"></i> 新建会话
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel -->
|
||||
<div class="flex-1 flex flex-col bg-slate-950">
|
||||
<!-- Tab Bar -->
|
||||
<div class="flex items-center bg-slate-900 border-b border-slate-800 px-2">
|
||||
<div class="flex items-center gap-1 py-1 flex-1">
|
||||
<div class="tab-active flex items-center gap-2 px-4 py-2 rounded-t-lg cursor-pointer group">
|
||||
<i class="fas fa-terminal text-cyan-400 text-xs"></i>
|
||||
<span class="text-slate-200 text-sm">Web-Prod-01</span>
|
||||
<div class="flex items-center gap-1 ml-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button class="p-1 hover:bg-slate-600 rounded" title="更多">
|
||||
<i class="fas fa-ellipsis-h text-slate-400 text-xs"></i>
|
||||
</button>
|
||||
<button class="p-1 hover:bg-slate-600 rounded" title="关闭">
|
||||
<i class="fas fa-times text-slate-400 text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 px-4 py-2 hover:bg-slate-800 rounded-t-lg cursor-pointer transition-colors">
|
||||
<i class="fas fa-folder text-yellow-500 text-xs"></i>
|
||||
<span class="text-slate-400 text-sm">SFTP</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-lg transition-colors">
|
||||
<i class="fas fa-plus text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Terminal Area -->
|
||||
<div class="flex-1 p-5 terminal-text text-sm overflow-auto" style="background: #0d1117;">
|
||||
<div class="text-green-400">➜ ~</div>
|
||||
<div class="text-slate-300 mt-2">ssh root@192.168.1.100</div>
|
||||
<div class="text-slate-500 mt-2">Connecting to 192.168.1.100...</div>
|
||||
<div class="text-green-400 mt-2">✓ Connected to CentOS 7.9</div>
|
||||
<div class="mt-2 text-slate-400">Last login: Mon Jan 15 10:30:25 2026 from <span class="text-green-400">172.16.2.1</span></div>
|
||||
<div class="text-cyan-400 mt-3 flex items-center gap-2">
|
||||
<span>[root@localhost ~]#</span>
|
||||
<span class="animate-pulse">_</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Bar -->
|
||||
<div class="bg-slate-900 border-t border-slate-800 px-4 py-2.5 flex items-center justify-between text-xs">
|
||||
<div class="flex items-center gap-6">
|
||||
<span class="text-green-500 flex items-center gap-1.5">
|
||||
<i class="fas fa-circle text-[8px]"></i>已连接
|
||||
</span>
|
||||
<span class="text-cyan-400 flex items-center gap-1.5">
|
||||
<i class="fas fa-wifi"></i>12ms
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-6 text-slate-500">
|
||||
<button class="hover:text-slate-300 flex items-center gap-1.5 transition-colors">
|
||||
<i class="fas fa-exchange-alt"></i>端口转发
|
||||
</button>
|
||||
<button class="hover:text-slate-300 flex items-center gap-1.5 transition-colors">
|
||||
<i class="fas fa-folder-open"></i>SFTP
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SFTP View -->
|
||||
<div id="view-sftp" class="preview-container hidden">
|
||||
<div class="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-2xl shadow-cyan-500/10 h-full flex flex-col">
|
||||
<!-- Title Bar -->
|
||||
<div class="bg-gradient-to-r from-slate-800 to-slate-700 px-4 py-3 flex items-center justify-between border-b border-slate-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<i class="fas fa-terminal text-cyan-400"></i>
|
||||
<span class="text-slate-200 font-medium">OpsXShell</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col min-h-0">
|
||||
<!-- Toolbar -->
|
||||
<div class="h-12 flex items-center justify-between px-4 border-b border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-slate-400">SFTP - root@192.168.1.100</span>
|
||||
<span class="px-2 py-0.5 text-xs rounded bg-green-500/20 text-green-400">已连接</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="flex items-center gap-2 px-4 py-1.5 bg-cyan-600 hover:bg-cyan-500 rounded-lg text-sm text-white transition-colors">
|
||||
<i class="fas fa-download text-xs"></i>下载
|
||||
</button>
|
||||
<button class="flex items-center gap-2 px-4 py-1.5 bg-green-600 hover:bg-green-500 rounded-lg text-sm text-white transition-colors">
|
||||
<i class="fas fa-upload text-xs"></i>上传
|
||||
</button>
|
||||
<button class="p-2 text-slate-400 hover:text-slate-200 hover:bg-slate-800 rounded-lg transition-colors">
|
||||
<i class="fas fa-sync-alt text-xs"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dual Pane File Manager -->
|
||||
<div class="flex-1 flex min-h-0">
|
||||
<!-- Local Panel -->
|
||||
<div class="flex-1 flex flex-col border-r border-slate-800">
|
||||
<div class="h-12 flex items-center justify-between px-4 border-b border-slate-800 bg-slate-800/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fas fa-hdd text-cyan-400"></i>
|
||||
<span class="font-medium text-slate-200">本地</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center gap-2 px-4 border-b border-slate-800">
|
||||
<div class="flex items-center gap-1 text-sm text-slate-300">
|
||||
<i class="fas fa-home text-slate-400"></i>
|
||||
<span class="text-slate-500">›</span>
|
||||
<span>Users</span>
|
||||
<span class="text-slate-500">›</span>
|
||||
<span>admin</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-9 flex items-center px-4 bg-slate-800/30 text-xs text-slate-500 border-b border-slate-800">
|
||||
<div class="flex-1 min-w-0">名称</div>
|
||||
<div class="w-32 text-right">修改时间</div>
|
||||
<div class="w-24 text-right">大小</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-folder text-sky-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">Desktop</span>
|
||||
</div>
|
||||
<div class="w-32 text-right text-xs text-slate-500">2026/01/15</div>
|
||||
<div class="w-24 text-right text-xs text-slate-500">--</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-folder text-sky-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">Documents</span>
|
||||
</div>
|
||||
<div class="w-32 text-right text-xs text-slate-500">2026/01/14</div>
|
||||
<div class="w-24 text-right text-xs text-slate-500">--</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50 bg-cyan-500/10">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-file text-slate-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">deploy.sh</span>
|
||||
</div>
|
||||
<div class="w-32 text-right text-xs text-slate-500">2026/01/15</div>
|
||||
<div class="w-24 text-right text-xs text-slate-500">2.4 KB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Remote Panel -->
|
||||
<div class="flex-1 flex flex-col">
|
||||
<div class="h-12 flex items-center justify-between px-4 border-b border-slate-800 bg-slate-800/50">
|
||||
<div class="flex items-center gap-3">
|
||||
<i class="fas fa-desktop text-cyan-400"></i>
|
||||
<span class="font-medium text-slate-200">192.168.1.100</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center gap-2 px-4 border-b border-slate-800">
|
||||
<div class="flex items-center gap-1 text-sm text-slate-300">
|
||||
<i class="fas fa-hdd text-slate-400"></i>
|
||||
<span class="text-slate-500">›</span>
|
||||
<span>root</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-9 flex items-center px-4 bg-slate-800/30 text-xs text-slate-500 border-b border-slate-800">
|
||||
<div class="flex-1 min-w-0">名称</div>
|
||||
<div class="w-32 text-right">修改时间</div>
|
||||
<div class="w-24 text-right">大小</div>
|
||||
</div>
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-folder text-sky-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">..</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-folder text-sky-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">logs</span>
|
||||
</div>
|
||||
<div class="w-32 text-right text-xs text-slate-500">2026/01/15</div>
|
||||
<div class="w-24 text-right text-xs text-slate-500">--</div>
|
||||
</div>
|
||||
<div class="h-10 flex items-center px-4 hover:bg-slate-800/50 cursor-pointer border-b border-slate-800/50">
|
||||
<div class="flex-1 flex items-center gap-2 min-w-0">
|
||||
<i class="fas fa-file text-slate-400 w-4"></i>
|
||||
<span class="text-sm text-slate-300">nginx.conf</span>
|
||||
</div>
|
||||
<div class="w-32 text-right text-xs text-slate-500">2026/01/14</div>
|
||||
<div class="w-24 text-right text-xs text-slate-500">1.2 KB</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transfer Panel -->
|
||||
<div class="h-40 border-t border-slate-800 bg-slate-900">
|
||||
<div class="h-10 flex items-center justify-between px-4 bg-slate-800/50">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fas fa-chevron-down text-slate-400 text-xs"></i>
|
||||
<span class="text-sm text-slate-300">传输队列</span>
|
||||
<span class="px-2 py-0.5 text-xs bg-cyan-500/20 text-cyan-400 rounded">2</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 space-y-1">
|
||||
<div class="flex items-center gap-3 px-3 py-2 bg-slate-800/30 rounded">
|
||||
<i class="fas fa-download text-cyan-400 w-4"></i>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-slate-300">nginx.conf</div>
|
||||
<div class="h-1.5 bg-slate-700 rounded-full mt-1 overflow-hidden">
|
||||
<div class="h-full bg-cyan-400" style="width: 75%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">75%</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3 px-3 py-2 bg-slate-800/30 rounded">
|
||||
<i class="fas fa-upload text-green-400 w-4"></i>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm text-slate-300">deploy.sh</div>
|
||||
<div class="h-1.5 bg-slate-700 rounded-full mt-1 overflow-hidden">
|
||||
<div class="h-full bg-green-400" style="width: 30%"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-xs text-slate-500">30%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Port Forward View -->
|
||||
<div id="view-portforward" class="preview-container hidden">
|
||||
<div class="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-2xl shadow-cyan-500/10 h-full flex flex-col">
|
||||
<!-- Title Bar -->
|
||||
<div class="bg-gradient-to-r from-slate-800 to-slate-700 px-4 py-3 flex items-center justify-between border-b border-slate-700">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex gap-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 ml-4">
|
||||
<i class="fas fa-terminal text-cyan-400"></i>
|
||||
<span class="text-slate-200 font-medium">OpsXShell</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div class="flex-1 flex flex-col min-h-0">
|
||||
<!-- Header -->
|
||||
<div class="h-14 flex items-center justify-between px-6 border-b border-slate-800 bg-slate-900">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-10 h-10 bg-orange-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-exchange-alt text-orange-400"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-semibold text-white">端口转发</div>
|
||||
<div class="text-sm text-gray-500">已连接: root@192.168.1.100</div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="flex items-center gap-2 px-4 py-2 bg-orange-600 hover:bg-orange-500 rounded-lg text-sm font-medium text-white transition-colors">
|
||||
<i class="fas fa-plus mr-1"></i>添加规则
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Rules List -->
|
||||
<div class="flex-1 overflow-y-auto p-6">
|
||||
<!-- Active Rule - Local Forward -->
|
||||
<div class="p-4 rounded-lg border border-green-500/30 bg-green-500/10 mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-blue-500/20 flex items-center justify-center text-blue-400">
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-white">MySQL 本地转发</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-blue-500/20 text-blue-400">本地转发</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-green-500/20 text-green-400">运行中</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mt-1">
|
||||
127.0.0.1:3306 → 192.168.1.100:3306
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-2 text-yellow-400 hover:bg-yellow-400/10 rounded" title="停止">
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
<button class="p-2 text-red-400 hover:bg-red-400/10 rounded" title="删除">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active Rule - Dynamic SOCKS5 -->
|
||||
<div class="p-4 rounded-lg border border-green-500/30 bg-green-500/10 mb-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center text-orange-400">
|
||||
<i class="fas fa-globe"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-white">SOCKS5 代理</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-orange-500/20 text-orange-400">动态 SOCKS5</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-green-500/20 text-green-400">运行中</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mt-1">
|
||||
SOCKS5 代理: 127.0.0.1:1080
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-2 text-yellow-400 hover:bg-yellow-400/10 rounded" title="停止">
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
<button class="p-2 text-red-400 hover:bg-red-400/10 rounded" title="删除">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inactive Rule - Remote Forward -->
|
||||
<div class="p-4 rounded-lg border border-slate-700 bg-slate-800">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="w-10 h-10 rounded-lg bg-purple-500/20 flex items-center justify-center text-purple-400">
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-white">Redis 远程转发</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded bg-purple-500/20 text-purple-400">远程转发</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mt-1">
|
||||
远程 6379 → 本地 127.0.0.1:6379
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<button class="p-2 text-green-400 hover:bg-green-400/10 rounded" title="启动">
|
||||
<i class="fas fa-play"></i>
|
||||
</button>
|
||||
<button class="p-2 text-red-400 hover:bg-red-400/10 rounded" title="删除">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Panel -->
|
||||
<div class="h-36 border-t border-slate-800 bg-slate-800/50 p-4">
|
||||
<div class="text-sm text-slate-400">
|
||||
<p class="mb-2"><i class="fas fa-info-circle mr-2 text-cyan-400"></i><strong class="text-slate-300">端口转发类型说明:</strong></p>
|
||||
<ul class="space-y-1 ml-6 text-xs">
|
||||
<li>• <strong class="text-blue-400">本地转发</strong>: 将本地端口映射到远程服务器 (访问内网MySQL/Redis等)</li>
|
||||
<li>• <strong class="text-purple-400">远程转发</strong>: 将远程端口映射到本地 (让外网访问本地服务)</li>
|
||||
<li>• <strong class="text-orange-400">动态 SOCKS5</strong>: 创建 SOCKS5 代理服务器 (浏览器代理/科学上网)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Features List View -->
|
||||
<div id="view-features" class="preview-container hidden">
|
||||
<div class="bg-slate-900 rounded-2xl border border-slate-800 overflow-hidden shadow-2xl h-full p-8 overflow-y-auto">
|
||||
<h2 class="text-2xl font-bold text-white mb-6 flex items-center gap-3">
|
||||
<i class="fas fa-list-check text-cyan-400"></i>
|
||||
功能实现清单
|
||||
</h2>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- SSH Terminal -->
|
||||
<div class="feature-done bg-slate-800/50 rounded-xl p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 bg-cyan-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-terminal text-cyan-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white">SSH 终端</h3>
|
||||
</div>
|
||||
<ul class="space-y-3 text-sm">
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>密码认证</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>密钥认证 (RSA/ED25519)</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>XTerm.js 高性能终端</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>多主题配色 (Dracula/Monokai/Solarized/One Dark)</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>IP 地址高亮显示</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-yellow-400">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span>智能命令补全 (规划中)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- SFTP -->
|
||||
<div class="feature-done bg-slate-800/50 rounded-xl p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 bg-green-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-folder-open text-green-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white">SFTP 管理</h3>
|
||||
</div>
|
||||
<ul class="space-y-3 text-sm">
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>双栏文件管理器</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>上传/下载文件</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>传输队列管理</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>传输进度显示</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-yellow-400">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span>拖拽上传下载 (进行中)</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-gray-400">
|
||||
<i class="fas fa-circle text-[8px]"></i>
|
||||
<span>断点续传 (规划中)</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Port Forward -->
|
||||
<div class="feature-done bg-slate-800/50 rounded-xl p-6">
|
||||
<div class="flex items-center gap-3 mb-4">
|
||||
<div class="w-10 h-10 bg-orange-500/20 rounded-lg flex items-center justify-center">
|
||||
<i class="fas fa-network-wired text-orange-400"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white">端口转发</h3>
|
||||
</div>
|
||||
<ul class="space-y-3 text-sm">
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>本地端口转发</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>远程端口转发</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>动态 SOCKS5 代理</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>转发规则管理</span>
|
||||
</li>
|
||||
<li class="flex items-center gap-2 text-green-400">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
<span>规则持久化存储</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Architecture Info -->
|
||||
<div class="mt-8 p-6 bg-slate-800/30 rounded-xl border border-slate-700">
|
||||
<h3 class="text-lg font-semibold text-white mb-4 flex items-center gap-2">
|
||||
<i class="fas fa-sitemap text-cyan-400"></i>
|
||||
技术架构
|
||||
</h3>
|
||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">Electron 28+</div>
|
||||
<div class="text-slate-500 text-xs">跨平台桌面框架</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">React 18</div>
|
||||
<div class="text-slate-500 text-xs">UI 组件库</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">TypeScript 5</div>
|
||||
<div class="text-slate-500 text-xs">类型安全</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">ssh2</div>
|
||||
<div class="text-slate-500 text-xs">SSH/SFTP 协议</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">XTerm.js</div>
|
||||
<div class="text-slate-500 text-xs">终端仿真</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">Zustand</div>
|
||||
<div class="text-slate-500 text-xs">状态管理</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">Tailwind CSS</div>
|
||||
<div class="text-slate-500 text-xs">样式框架</div>
|
||||
</div>
|
||||
<div class="text-center p-4 bg-slate-800 rounded-lg">
|
||||
<div class="text-cyan-400 font-semibold mb-1">SQLite</div>
|
||||
<div class="text-slate-500 text-xs">数据持久化</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="text-center text-slate-500 text-sm mt-8">
|
||||
<p>OpsXShell v0.3.0 | 专业级 SSH/SFTP 客户端</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function showView(view) {
|
||||
// Hide all views
|
||||
const views = ['terminal', 'sftp', 'portforward', 'features'];
|
||||
views.forEach(v => {
|
||||
document.getElementById('view-' + v).classList.add('hidden');
|
||||
});
|
||||
|
||||
// Reset all tab styles
|
||||
views.forEach(tab => {
|
||||
const el = document.getElementById('tab-' + tab);
|
||||
if (el) {
|
||||
el.classList.remove('bg-cyan-600', 'text-white');
|
||||
el.classList.add('bg-slate-800', 'text-slate-300');
|
||||
}
|
||||
});
|
||||
|
||||
// Show selected view
|
||||
document.getElementById('view-' + view).classList.remove('hidden');
|
||||
document.getElementById('tab-' + view).classList.remove('bg-slate-800', 'text-slate-300');
|
||||
document.getElementById('tab-' + view).classList.add('bg-cyan-600', 'text-white');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,118 @@
|
||||
# 使用指南
|
||||
|
||||
## 快速连接
|
||||
|
||||
按 `Cmd/Ctrl + K`,输入 `user@host` 或 `user@host:port` 后连接。默认端口为 `22`。快速连接适合临时使用,不会自动保存会话。
|
||||
|
||||
需要长期使用时新建保存会话,选择密码或私钥认证,并按需设置分组、标签和启动命令。未填写密码或私钥的导入会话会尝试本机 SSH Agent 和常见默认密钥。
|
||||
|
||||
安装版支持系统链接,例如:
|
||||
|
||||
```text
|
||||
ssh://root@opsbase.cn:55555
|
||||
```
|
||||
|
||||
点击后会唤起 OpsXShell,并打开已填好目标的快速连接窗口。为了避免网页静默连接陌生服务器,仍需用户确认连接;链接中禁止携带密码、远程路径、查询参数或片段。
|
||||
|
||||
## 代理与跳板
|
||||
|
||||
会话的高级选项支持:
|
||||
|
||||
- HTTP CONNECT 代理
|
||||
- SOCKS5 代理
|
||||
- 可选代理用户名和密码
|
||||
- 选择一个已保存会话作为跳板机
|
||||
|
||||
代理和跳板互斥,选择其中一个会清除另一个,避免连接路径含糊。界面保持单跳操作;Rust 连接层可处理已有配置中的最多 8 跳链。
|
||||
|
||||
## 端口转发与 SOCKS5
|
||||
|
||||
端口转发页面支持本地转发、远程转发和动态 SOCKS5。普通用户可直接点击“启动 SOCKS5”,默认创建:
|
||||
|
||||
```text
|
||||
socks5://127.0.0.1:1080
|
||||
```
|
||||
|
||||
- 启动后卡片显示真实绑定端口、活动连接数和上下行流量。
|
||||
- 复制按钮复制 `socks5://127.0.0.1:<端口>`。需要远程 DNS 解析的命令行工具应使用 `socks5h`,例如 `curl --proxy socks5h://127.0.0.1:1080 https://example.com`。
|
||||
- “停止”会关闭 Rust Listener,不是只改界面图标。删除运行中的规则会先停止再删除。
|
||||
- “复制规则”会创建独立的新规则,便于只修改端口或目标;不会复制原规则的运行状态。
|
||||
- 运行中的规则选择“编辑”时,会先提示停止真实监听,再允许保存新配置。
|
||||
- 保存的是规则,不是“运行中”状态;应用重启后需要用户再次启动。
|
||||
- SOCKS5 默认仅监听本机,当前不提供局域网开放和客户端密码认证。
|
||||
|
||||
## 导入 OpenSSH 配置
|
||||
|
||||
从应用菜单选择“文件 > 导入会话”,打开 `~/.ssh/config`。当前解析以下常用字段:
|
||||
|
||||
- `Host`
|
||||
- `HostName`
|
||||
- `User`
|
||||
- `Port`
|
||||
- `IdentityFile`
|
||||
- 单个 `ProxyJump` 别名
|
||||
|
||||
包含 `*`、`?` 或 `!` 的 Host 模式不会生成会话。导入后请检查目标、用户名、密钥路径和跳板关系再连接。
|
||||
|
||||
会话导出为 JSON,不包含密码、私钥口令或代理密码;私钥路径会保留,便于在同一台电脑恢复配置。
|
||||
|
||||
## SFTP
|
||||
|
||||
SFTP 使用双栏文件管理器。上传和下载先写入 `.opsx-part` 临时文件,中断后再次传输同一路径会从已有字节继续。传输完成并通过大小校验后才替换正式文件,因此失败传输不会伪装成完整文件。
|
||||
|
||||
- 上传:在左侧本地栏选中文件后点击“上传”;未选择文件时点击“上传”会打开系统原生文件选择器。
|
||||
- 下载:在右侧远程栏选中文件后点击“下载”。下载按钮在未选择远程文件时保持禁用。
|
||||
- 文件右键菜单也提供对应的“上传”或“下载”操作。
|
||||
- 可直接把左侧本地文件拖到右侧远程栏上传,也可把右侧远程文件拖到左侧本地栏下载;拖到目录行时以该目录为目标,并显示落点高亮。
|
||||
- 当前拖拽只支持单个文件,不递归传输目录;同侧拖拽不会执行移动或重命名。
|
||||
- 远程文本编辑的“打开/保存”直接读写远程文件,不会在本地创建同名文件,也不等同于上传或下载。
|
||||
- 传输失败会在传输队列和界面通知中显示 Rust/SFTP 返回的具体原因。
|
||||
|
||||
当前目标文件采用覆盖语义。内容哈希校验和显式冲突选择仍在路线图中。
|
||||
|
||||
## 终端与批量命令
|
||||
|
||||
- `Cmd/Ctrl + D`:分屏当前终端
|
||||
- `Cmd/Ctrl + W`:关闭当前标签
|
||||
- 会话侧栏进入批量模式后,可同时打开终端、SFTP,或在多个主机执行同一条命令
|
||||
|
||||
批量命令结果只保留在当前弹窗。OpsXShell 不持久化终端命令历史。
|
||||
|
||||
## 本地终端与串口
|
||||
|
||||
快速启动入口可打开系统默认 Shell。串口入口会列出系统设备,可设置波特率、数据位、停止位和校验方式后连接终端设备。
|
||||
|
||||
## 大模型设置与终端助手
|
||||
|
||||
在“设置 → 大模型”中添加 Provider。内置预设包含 DeepSeek、Z.AI/GLM、MiniMax、Kimi、Qwen、OpenAI、Anthropic、OpenRouter、Ollama 和 new-api;也可以从空白配置开始。
|
||||
|
||||
1. 选择供应商预设或“自定义”。新配置只在当前表单中暂存。
|
||||
2. 选择模型,核对 Endpoint,输入 API Key。协议和 HTTP 开关位于默认收起的“高级设置”。
|
||||
3. 点击“测试连接”。Rust 使用当前表单实际向目标模型发送“你是什么模型?请简短回答。”,但不会保存 Provider 或 API Key。只有服务返回成功状态且回答非空时界面才显示 `HTTP 200`。
|
||||
4. 测试成功后“保存”按钮才会启用。Endpoint、模型、协议或凭据变化后,测试结果立即失效,必须重新测试。
|
||||
5. 保存时 API Key 进入 Rust 加密存储,普通设置 JSON 不包含明文,界面重新打开后也不会回显。
|
||||
|
||||
预设模型参考 models.dev 与各供应商兼容接口维护。new-api、One API、OpenRouter 等网关使用“自定义”或相应预设,填写自己的 `/v1` Endpoint 和实际模型 ID。
|
||||
|
||||
终端工具栏点击“AI”打开助手:
|
||||
|
||||
- 终端已有选区时,只发送选区。
|
||||
- 没有选区时,默认不发送任何终端内容;勾选“无选区时附带当前屏幕”才会读取当前焦点分屏的可见行。
|
||||
- Rust 会在联网前做常见敏感信息脱敏,面板会显示发送行数和脱敏数量。
|
||||
- AI 结果只提供复制;当回答中恰好有一个单行 shell 代码块时,额外提供“填入终端”。填入不会附加回车,仍需人工检查和执行。
|
||||
- 多行脚本不允许直接填入。关闭面板后问题、上下文和回答全部清空。
|
||||
- “执行任务”固定复用当前已连接的 SSH 会话。AI 先准备唯一的单行远程命令,界面显示目标与命令;用户再次确认后才调用原生 `ssh-exec` 执行一次。
|
||||
- 执行后的真实输出会回给 AI 生成简短结论。每次后续命令都必须重新确认,命令与输出不写入历史。
|
||||
- 不提供独立 podexec。远程服务器已有的 `kubectl`、`helm`、`docker` 等工具都通过同一 SSH 执行链使用,避免额外管理本机 kubeconfig 和执行目标。
|
||||
|
||||
云模型会把所选上下文发给相应供应商;有严格数据边界时使用本机 Ollama,并仅在可信本机上开启 HTTP。OpsXShell 不保存 AI 对话、终端上下文或长期记忆。
|
||||
|
||||
## 本地数据
|
||||
|
||||
设置“关于”页会显示当前系统的实际数据路径。会话密码、私钥口令、代理密码和大模型 API Key 使用 AES-256-GCM 加密。主密钥位于:
|
||||
|
||||
```text
|
||||
~/.opsxshsell/master.key
|
||||
```
|
||||
|
||||
Unix 系统中目录权限为 `0700`,密钥文件权限为 `0600`。主机信任记录使用 `~/.ssh/known_hosts`。
|
||||
Reference in new issue
Block a user