py-web-ssh 0.1.3__tar.gz → 0.1.4__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: py-web-ssh
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A Python web SSH client with xterm.js, reconnectable sessions, logs, and file transfer.
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "py-web-ssh"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "A Python web SSH client with xterm.js, reconnectable sessions, logs, and file transfer."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -2,4 +2,4 @@
2
2
 
3
3
  __all__ = ["__version__"]
4
4
 
5
- __version__ = "0.1.3"
5
+ __version__ = "0.1.4"
@@ -164,15 +164,31 @@ document.querySelector("#upload-form").addEventListener("submit", async (event)
164
164
  setStatus(response.ok ? "上传完成" : "上传失败");
165
165
  });
166
166
 
167
- document.querySelector("#download-form").addEventListener("submit", (event) => {
167
+ document.querySelector("#download-form").addEventListener("submit", async (event) => {
168
168
  event.preventDefault();
169
169
  const remotePath = valueOf("#download-path");
170
170
  if (!activeSessionId || !remotePath) {
171
171
  appendLogLine("下载需要会话 UUID 和远端路径。");
172
172
  return;
173
173
  }
174
- window.location.href =
175
- `/api/sessions/${activeSessionId}/files/download?remote_path=${encodeURIComponent(remotePath)}`;
174
+ setStatus("下载中...");
175
+ const response = await fetch(
176
+ `/api/sessions/${activeSessionId}/files/download?remote_path=${encodeURIComponent(remotePath)}`,
177
+ );
178
+ if (!response.ok) {
179
+ appendLogLine(`下载失败: ${await response.text()}`);
180
+ setStatus("下载失败");
181
+ return;
182
+ }
183
+ const blob = await response.blob();
184
+ const link = document.createElement("a");
185
+ link.href = URL.createObjectURL(blob);
186
+ link.download = filenameFromResponse(response, remotePath);
187
+ document.body.appendChild(link);
188
+ link.click();
189
+ link.remove();
190
+ URL.revokeObjectURL(link.href);
191
+ setStatus("下载完成");
176
192
  });
177
193
 
178
194
  document.querySelectorAll(".tab").forEach((button) => {
@@ -367,6 +383,13 @@ function checked(selector) {
367
383
  return document.querySelector(selector).checked;
368
384
  }
369
385
 
386
+ function filenameFromResponse(response, fallbackPath) {
387
+ const disposition = response.headers.get("Content-Disposition") || "";
388
+ const match = disposition.match(/filename="([^"]+)"/);
389
+ if (match) return match[1];
390
+ return fallbackPath.split("/").filter(Boolean).pop() || "download.bin";
391
+ }
392
+
370
393
  function bytesToBase64(bytes) {
371
394
  let binary = "";
372
395
  const size = 0x8000;
@@ -30,46 +30,48 @@
30
30
  <button class="panel-toggle" type="button" aria-expanded="false" aria-controls="connect-panel">
31
31
  连接
32
32
  </button>
33
- <form id="connect-panel" class="stack panel-body" hidden>
33
+ <div id="connect-panel" class="stack panel-body" hidden>
34
34
  <div class="section-tabs" aria-label="连接栏目">
35
35
  <button class="section-tab active" type="button">连接</button>
36
36
  <button class="section-tab" type="button" data-open-panel="session-panel">会话</button>
37
37
  <button class="section-tab" type="button" data-open-panel="files-panel">文件</button>
38
38
  </div>
39
- <label>
40
- 目标服务器
41
- <input id="host" name="host" required autocomplete="off" placeholder="192.168.1.10" />
42
- </label>
43
- <div class="grid-2">
39
+ <form id="connect-form" class="stack">
44
40
  <label>
45
- 端口
46
- <input id="port" name="port" type="number" min="1" max="65535" value="22" required />
41
+ 目标服务器
42
+ <input id="host" name="host" required autocomplete="off" placeholder="192.168.1.10" />
47
43
  </label>
44
+ <div class="grid-2">
45
+ <label>
46
+ 端口
47
+ <input id="port" name="port" type="number" min="1" max="65535" value="22" required />
48
+ </label>
49
+ <label>
50
+ 用户名
51
+ <input id="username" name="username" required autocomplete="username" />
52
+ </label>
53
+ </div>
48
54
  <label>
49
- 用户名
50
- <input id="username" name="username" required autocomplete="username" />
55
+ 口令
56
+ <input id="password" name="password" type="password" autocomplete="current-password" />
51
57
  </label>
52
- </div>
53
- <label>
54
- 口令
55
- <input id="password" name="password" type="password" autocomplete="current-password" />
56
- </label>
57
- <label>
58
- 私钥文件
59
- <input id="private-key-file" name="private-key-file" type="file" />
60
- </label>
61
- <label>
62
- 私钥口令
63
- <input id="private-key-passphrase" name="private-key-passphrase" type="password" />
64
- </label>
65
- <div class="checks">
66
- <label><input id="legacy-algorithms" type="checkbox" checked /> legacy 算法</label>
67
- <label><input id="allow-agent" type="checkbox" /> SSH agent</label>
68
- <label><input id="look-for-keys" type="checkbox" /> 服务端本机密钥</label>
69
- <label><input id="strict-host-key" type="checkbox" /> known_hosts 校验</label>
70
- </div>
71
- <button class="primary" type="submit">连接</button>
72
- </form>
58
+ <label>
59
+ 私钥文件
60
+ <input id="private-key-file" name="private-key-file" type="file" />
61
+ </label>
62
+ <label>
63
+ 私钥口令
64
+ <input id="private-key-passphrase" name="private-key-passphrase" type="password" />
65
+ </label>
66
+ <div class="checks">
67
+ <label><input id="legacy-algorithms" type="checkbox" checked /> legacy 算法</label>
68
+ <label><input id="allow-agent" type="checkbox" /> SSH agent</label>
69
+ <label><input id="look-for-keys" type="checkbox" /> 服务端本机密钥</label>
70
+ <label><input id="strict-host-key" type="checkbox" /> known_hosts 校验</label>
71
+ </div>
72
+ <button class="primary" type="submit">连接</button>
73
+ </form>
74
+ </div>
73
75
  </section>
74
76
 
75
77
  <section class="panel collapsible-panel">
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes