@feng3d/ctc 0.0.20 → 0.1.0

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.
Files changed (56) hide show
  1. package/README.md +82 -106
  2. package/dist/cli.d.ts +13 -12
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +188 -966
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config.d.ts +18 -51
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +42 -180
  9. package/dist/config.js.map +1 -1
  10. package/dist/controller.d.ts +47 -5
  11. package/dist/controller.d.ts.map +1 -1
  12. package/dist/controller.js +10 -23
  13. package/dist/controller.js.map +1 -1
  14. package/dist/handlers/unified-handler.d.ts.map +1 -1
  15. package/dist/handlers/unified-handler.js +3 -1
  16. package/dist/handlers/unified-handler.js.map +1 -1
  17. package/dist/index.d.ts +7 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +5 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/proxy-manager.d.ts +32 -32
  22. package/dist/proxy-manager.d.ts.map +1 -1
  23. package/dist/proxy-manager.js +81 -111
  24. package/dist/proxy-manager.js.map +1 -1
  25. package/package.json +5 -10
  26. package/admin-ui/dist/app.js +0 -29
  27. package/admin-ui/dist/index.html +0 -222
  28. package/admin-ui/dist/style.css +0 -1
  29. package/dist/admin-server.d.ts +0 -149
  30. package/dist/admin-server.d.ts.map +0 -1
  31. package/dist/admin-server.js +0 -426
  32. package/dist/admin-server.js.map +0 -1
  33. package/dist/app.d.ts +0 -17
  34. package/dist/app.d.ts.map +0 -1
  35. package/dist/app.js +0 -97
  36. package/dist/app.js.map +0 -1
  37. package/dist/daemon-entry.d.ts +0 -8
  38. package/dist/daemon-entry.d.ts.map +0 -1
  39. package/dist/daemon-entry.js +0 -15
  40. package/dist/daemon-entry.js.map +0 -1
  41. package/dist/daemon.d.ts +0 -32
  42. package/dist/daemon.d.ts.map +0 -1
  43. package/dist/daemon.js +0 -370
  44. package/dist/daemon.js.map +0 -1
  45. package/dist/forward-proxy.d.ts +0 -100
  46. package/dist/forward-proxy.d.ts.map +0 -1
  47. package/dist/forward-proxy.js +0 -407
  48. package/dist/forward-proxy.js.map +0 -1
  49. package/dist/ipc-handler.d.ts +0 -124
  50. package/dist/ipc-handler.d.ts.map +0 -1
  51. package/dist/ipc-handler.js +0 -305
  52. package/dist/ipc-handler.js.map +0 -1
  53. package/dist/process-utils.d.ts +0 -31
  54. package/dist/process-utils.d.ts.map +0 -1
  55. package/dist/process-utils.js +0 -87
  56. package/dist/process-utils.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feng3d/ctc",
3
- "version": "0.0.20",
3
+ "version": "0.1.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -11,19 +11,14 @@
11
11
  ".": {
12
12
  "import": "./dist/index.js",
13
13
  "types": "./dist/index.d.ts"
14
- },
15
- "./app": {
16
- "import": "./dist/app.js"
17
14
  }
18
15
  },
19
16
  "files": [
20
- "dist",
21
- "admin-ui/dist"
17
+ "dist"
22
18
  ],
23
19
  "scripts": {
24
- "start": "node dist/app.js",
25
- "build": "npm run build:admin-ui && tsc",
26
- "build:admin-ui": "cd admin-ui && npm run build",
20
+ "start": "node dist/cli.js",
21
+ "build": "tsc",
27
22
  "watch": "tsc --watch --pretty",
28
23
  "clean": "rm -rf dist",
29
24
  "test": "vitest run",
@@ -31,7 +26,7 @@
31
26
  "test:coverage": "vitest run --coverage"
32
27
  },
33
28
  "dependencies": {
34
- "@feng3d/chuantou-shared": "^0.0.20",
29
+ "@feng3d/chuantou-shared": "^0.1.0",
35
30
  "chalk": "^5.6.2",
36
31
  "commander": "^14.0.3",
37
32
  "uuid": "^9.0.1",
@@ -1,29 +0,0 @@
1
- (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))o(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&o(a)}).observe(document,{childList:!0,subtree:!0});function n(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(s){if(s.ep)return;s.ep=!0;const r=n(s);fetch(s.href,r)}})();let p=null,h=null,E="reverse",I="";const m=new Map;let x=null;function S(t){const e=Math.floor(t/1e3),n=Math.floor(e/60),o=Math.floor(n/60),s=Math.floor(o/24);return s>0?`${s}天 ${o%24}小时`:o>0?`${o}小时 ${n%60}分钟`:n>0?`${n}分钟 ${e%60}秒`:`${e}秒`}function c(t,e="success"){const n=document.getElementById("toast");n.textContent=t,n.className=`toast ${e} show`,setTimeout(()=>n.classList.remove("show"),3e3)}async function w(){try{const n=(await(await fetch("/_ctc/status")).json()).proxies||[];if(n.length===0)return;for(const s of n)m.set(s.remotePort,"testing");v();const o=n.map(async s=>{const r=s.remotePort;try{const a=await fetch(`/_ctc/test-proxy?port=${r}`,{method:"POST"});a.ok&&(await a.json()).success?m.set(r,"connected"):m.set(r,"failed")}catch{m.set(r,"failed")}v()});await Promise.all(o)}catch(t){console.error("测试代理连接失败:",t)}}function j(t){var e,n,o,s,r,a;E=t,document.querySelectorAll(".tab").forEach(i=>i.classList.remove("active")),t==="reverse"?(e=document.getElementById("tabReverse"))==null||e.classList.add("active"):t==="forward"?(n=document.getElementById("tabForward"))==null||n.classList.add("active","forward-tab"):(o=document.getElementById("tabGuide"))==null||o.classList.add("active"),(s=document.getElementById("reversePanel"))==null||s.classList.toggle("hidden",t!=="reverse"),(r=document.getElementById("forwardPanel"))==null||r.classList.toggle("hidden",t!=="forward"),(a=document.getElementById("guidePanel"))==null||a.classList.toggle("hidden",t!=="guide"),t==="reverse"&&w()}function M(){x&&clearInterval(x),x=setInterval(()=>{E==="reverse"&&w()},1e4)}async function v(){try{const e=await(await fetch("/_ctc/status")).json(),n=document.getElementById("status");n&&(e.running?(n.textContent=e.authenticated?"已连接":e.connected?"认证中...":"连接中...",n.className="status running"):(n.textContent="已停止",n.className="status stopped"));const o=document.getElementById("server");o&&(o.textContent=e.serverUrl.replace("ws://","").replace("wss://",""));const s=document.getElementById("connection");s&&(s.textContent=e.authenticated?"已认证":e.connected?"已连接":"未连接");const r=document.getElementById("uptime");r&&(r.textContent=S(e.uptime));const a=document.getElementById("proxyCount");a&&(a.textContent=String(e.proxies.length));const i=document.getElementById("reconnectCount");i&&(i.textContent=String(e.reconnectAttempts));const d=document.getElementById("lastUpdate");d&&(d.textContent=new Date().toLocaleTimeString("zh-CN")),e.isRegistered!==void 0&&b(e.isRegistered);const u=document.getElementById("registerStatus");u&&e.clientId&&(u.textContent=`已注册 (ID: ${e.clientId})`);const f=new Set(e.proxies.map(l=>l.remotePort));for(const l of m.keys())f.has(l)||m.delete(l);const y=document.getElementById("proxiesList");y&&(e.proxies.length===0?y.innerHTML='<div class="empty-state">暂无反向代理映射,点击上方按钮添加</div>':y.innerHTML=e.proxies.map(l=>{const g=m.get(l.remotePort)||"unknown",B=g==="connected"?"status-connected":g==="failed"?"status-failed":g==="testing"?"status-testing":"status-unknown",T=g==="connected"?"✓":g==="failed"?"✗":g==="testing"?"⟳":"?";return`
2
- <div class="proxy-item">
3
- <div class="proxy-info">
4
- <span class="proxy-index">#${l.index||"-"}</span>
5
- <span class="proxy-protocol">ALL</span>
6
- <span class="proxy-remote">:${l.remotePort}</span>
7
- <span class="proxy-arrow">→</span>
8
- <span class="proxy-local">${l.localHost||"localhost"}:${l.localPort}</span>
9
- </div>
10
- <div class="proxy-status ${B}" title="连接状态">${T}</div>
11
- <button class="btn btn-danger" onclick="showDeleteModal(${l.remotePort}, 'reverse')">删除</button>
12
- </div>
13
- `}).join("")),e.forwardProxies&&$(e.forwardProxies)}catch(t){console.error("获取状态失败:",t)}}function b(t){const e=document.getElementById("registerStatus");e&&(t?(e.textContent="已注册",e.style.color="#00ff88"):(e.textContent="未注册",e.style.color="#888"))}function $(t){const e=document.getElementById("forwardCount");e&&(e.textContent=String(t.length));const n=document.getElementById("forwardList");n&&(t.length===0?n.innerHTML='<div class="empty-state">暂无正向穿透映射,点击上方按钮添加</div>':n.innerHTML=t.map(o=>`
14
- <div class="proxy-item forward-item">
15
- <div class="proxy-info">
16
- <span class="proxy-index">→</span>
17
- <span class="proxy-protocol">P2P</span>
18
- <span class="proxy-remote">:${o.localPort}</span>
19
- <span class="proxy-arrow">→</span>
20
- <span class="proxy-local">${o.targetClientId}:${o.targetPort}</span>
21
- </div>
22
- <button class="btn btn-danger" onclick="showDeleteModal(${o.localPort}, 'forward')">删除</button>
23
- </div>
24
- `).join(""))}function k(t){const e=document.getElementById("clientsSection"),n=document.getElementById("clientsList"),o=document.getElementById("targetClientId");if(!t||t.length===0){e&&e.classList.add("hidden"),o&&(o.innerHTML='<option value="">无在线客户端</option>');return}if(e&&e.classList.remove("hidden"),n&&(n.innerHTML=t.map(s=>`
25
- <div class="client-card ${s.id===I?"selected":""}" onclick="selectClient('${s.id}')">
26
- <div class="client-id">${s.id}</div>
27
- <div class="client-desc">${s.description||"无描述"}</div>
28
- </div>
29
- `).join("")),o){const s=o.value;o.innerHTML='<option value="">选择目标客户端</option>'+t.map(r=>`<option value="${r.id}">${r.id} (${r.description||"无描述"})</option>`).join(""),s&&(o.value=s)}}function O(t){if(I=t,document.querySelectorAll(".client-card").forEach(e=>e.classList.remove("selected")),event&&event.target){const e=event.target.closest(".client-card");e&&e.classList.add("selected")}}async function L(){try{const t=await fetch("/_ctc/forward/clients");if(t.ok){const e=await t.json();k(e.clients||[])}else{const e=await t.json();c(`获取客户端列表失败: ${e.error}`,"error")}}catch{c("获取客户端列表失败: 网络错误","error")}}async function D(){try{const e=await(await fetch("/_ctc/forward/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({description:"feng3d-ctc 客户端"})})).json();if(e.success){c("已注册到服务器");const n=document.getElementById("registerBtn");n&&(n.textContent="已注册",n.disabled=!0),L()}else c(`注册失败: ${e.error}`,"error")}catch{c("注册失败: 网络错误","error")}}function R(t,e="reverse"){var n;e==="reverse"?(p=t,h=null):(h=t,p=null),(n=document.getElementById("deleteModal"))==null||n.classList.add("show")}async function _(){const t=document.getElementById("reconnectBtn");if(!t)return;t.disabled=!0;const e=t.textContent;t.textContent="连接中...";try{const o=await(await fetch("/_ctc/reconnect",{method:"POST"})).json();o.success?(c(o.message||"正在重连...","success"),setTimeout(()=>v(),1e3)):c(`重连失败: ${o.error}`,"error")}catch{c("重连请求失败: 网络错误","error")}finally{t.disabled=!1,t.textContent=e}}async function H(){var t;if((t=document.getElementById("deleteModal"))==null||t.classList.remove("show"),p!==null)try{const e=await fetch(`/_ctc/proxies/${p}`,{method:"DELETE"});if(e.ok)c("反向代理已删除"),m.delete(p),v();else{const n=await e.json();c(`删除失败: ${n.error}`,"error")}}catch{c("删除失败: 网络错误","error")}if(h!==null)try{const n=await(await fetch("/_ctc/forward/remove",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({localPort:h})})).json();n.success?(c("正向穿透已删除"),P()):c(`删除失败: ${n.error}`,"error")}catch{c("删除失败: 网络错误","error")}p=null,h=null}function N(){const t=document.getElementById("showAddForm"),e=document.getElementById("cancelAdd"),n=document.getElementById("addProxy"),o=document.getElementById("addForm"),s=document.getElementById("testReverseProxy");t==null||t.addEventListener("click",()=>{o==null||o.classList.add("show")}),e==null||e.addEventListener("click",()=>{o==null||o.classList.remove("show")}),s==null||s.addEventListener("click",async()=>{try{const i=(await(await fetch("/_ctc/status")).json()).proxies||[];if(i.length===0){c("请先添加反向代理","error");return}c(`正在测试 ${i.length} 个反向代理...`),await w();const d=Array.from(m.values()).filter(f=>f==="connected").length,u=Array.from(m.values()).filter(f=>f==="failed").length;u===0?c(`测试完成: 全部成功 (${d}/${i.length})`):c(`测试完成: 成功 ${d}, 失败 ${u}`,"error")}catch(r){c(`测试失败: ${r.message}`,"error")}}),n==null||n.addEventListener("click",async()=>{const r=document.getElementById("newRemotePort"),a=document.getElementById("newLocalPort"),i=document.getElementById("newLocalHost"),d=parseInt(r.value),u=parseInt(a.value),f=i.value||"localhost";if(!d||!u){c("请填写完整信息","error");return}try{const y=await fetch("/_ctc/proxies",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({remotePort:d,localPort:u,localHost:f})});if(y.ok)c("反向代理已添加"),o==null||o.classList.remove("show"),r.value="",a.value="",i.value="localhost",await v(),await w();else{const l=await y.json();c(`添加失败: ${l.error}`,"error")}}catch{c("添加失败: 网络错误","error")}})}async function P(){try{const t=await fetch("/_ctc/forward/list");if(t.ok){const e=await t.json();$(e.proxies||[])}}catch(t){console.error("获取正向穿透列表失败:",t)}}function A(){const t=document.getElementById("refreshClients"),e=document.getElementById("showForwardForm"),n=document.getElementById("cancelForward"),o=document.getElementById("addForward"),s=document.getElementById("forwardForm");t==null||t.addEventListener("click",()=>{L()}),e==null||e.addEventListener("click",()=>{var a;s==null||s.classList.add("show"),(a=document.getElementById("clientsList"))!=null&&a.textContent.trim()||L()}),n==null||n.addEventListener("click",()=>{s==null||s.classList.remove("show"),I="",document.querySelectorAll(".client-card").forEach(a=>a.classList.remove("selected"))}),o==null||o.addEventListener("click",async()=>{const a=document.getElementById("forwardLocalPort"),i=document.getElementById("targetClientId"),d=document.getElementById("forwardTargetPort"),u=parseInt(a.value),f=i.value,y=parseInt(d.value);if(!u||!f||!y){c("请填写完整信息","error");return}try{const g=await(await fetch("/_ctc/forward/add",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({localPort:u,targetClientId:f,targetPort:y})})).json();g.success?(c("正向穿透已添加"),s==null||s.classList.remove("show"),a.value="",d.value="",i.value="",I="",P()):c(`添加失败: ${g.error}`,"error")}catch{c("添加失败: 网络错误","error")}});const r=document.getElementById("registerBtn");r==null||r.addEventListener("click",D)}function C(){const t=document.getElementById("cancelDelete"),e=document.getElementById("confirmDelete");t==null||t.addEventListener("click",()=>{var n;(n=document.getElementById("deleteModal"))==null||n.classList.remove("show"),p=null,h=null}),e==null||e.addEventListener("click",H),N(),A(),v(),setTimeout(()=>{E==="reverse"&&w()},500),setInterval(v,3e3),M(),E==="forward"&&(L(),P())}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",C):C();window.switchTab=j;window.showDeleteModal=R;window.selectClient=O;window.manualReconnect=_;
@@ -1,222 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="zh-CN">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>穿透客户端管理</title>
7
- <script type="module" crossorigin src="./app.js"></script>
8
- <link rel="stylesheet" crossorigin href="./style.css">
9
- </head>
10
- <body>
11
- <div class="container">
12
- <div class="header">
13
- <h1>🔌 feng3d-ctc 穿透客户端</h1>
14
- <div class="header-actions">
15
- <div class="status running" id="status">运行中</div>
16
- <button class="btn btn-primary btn-sm" id="reconnectBtn" onclick="window.manualReconnect()">主动连接</button>
17
- </div>
18
- </div>
19
-
20
- <div class="grid">
21
- <div class="card">
22
- <div class="card-label">服务器</div>
23
- <div class="card-value" id="server">-</div>
24
- </div>
25
- <div class="card">
26
- <div class="card-label">连接状态</div>
27
- <div class="card-value" id="connection">-</div>
28
- </div>
29
- <div class="card">
30
- <div class="card-label">运行时长</div>
31
- <div class="card-value"><span id="uptime">-</span></div>
32
- </div>
33
- <div class="card">
34
- <div class="card-label">反向代理</div>
35
- <div class="card-value"><span id="proxyCount">0</span><span class="unit"> 个</span></div>
36
- </div>
37
- <div class="card">
38
- <div class="card-label">正向穿透</div>
39
- <div class="card-value"><span id="forwardCount">0</span><span class="unit"> 个</span></div>
40
- </div>
41
- <div class="card">
42
- <div class="card-label">重连次数</div>
43
- <div class="card-value"><span id="reconnectCount">0</span><span class="unit"> 次</span></div>
44
- </div>
45
- </div>
46
-
47
- <!-- 标签切换 -->
48
- <div class="tabs">
49
- <button class="tab active" id="tabReverse" onclick="window.switchTab('reverse')">反向代理模式</button>
50
- <button class="tab" id="tabForward" onclick="window.switchTab('forward')">正向穿透模式</button>
51
- <button class="tab" id="tabGuide" onclick="window.switchTab('guide')">使用说明</button>
52
- </div>
53
-
54
- <!-- 反向代理面板 -->
55
- <div class="proxies-section" id="reversePanel">
56
- <div class="section-header">
57
- <div class="section-title">反向代理映射</div>
58
- <div class="header-buttons">
59
- <button class="btn btn-secondary btn-sm" id="testReverseProxy">测试连接</button>
60
- <button class="btn btn-primary btn-sm" id="showAddForm">+ 添加代理</button>
61
- </div>
62
- </div>
63
-
64
- <div class="section-info">
65
- <strong>反向代理模式</strong> — 将公网端口映射到本地服务,适用于需要将本地服务暴露到公网的场景。
66
- 访问 <code>http://服务器:端口</code> 即可访问本地服务。
67
- </div>
68
-
69
- <div class="add-form" id="addForm">
70
- <div class="form-row">
71
- <div class="form-group">
72
- <label>远程端口</label>
73
- <input type="number" id="newRemotePort" placeholder="8080" min="1" max="65535">
74
- </div>
75
- <div class="form-group">
76
- <label>本地端口</label>
77
- <input type="number" id="newLocalPort" placeholder="3000" min="1" max="65535">
78
- </div>
79
- <div class="form-group">
80
- <label>本地地址</label>
81
- <input type="text" id="newLocalHost" placeholder="localhost">
82
- </div>
83
- <div class="form-actions">
84
- <button class="btn btn-primary" id="addProxy">添加</button>
85
- <button class="btn btn-secondary" id="cancelAdd">取消</button>
86
- </div>
87
- </div>
88
- </div>
89
-
90
- <div id="proxiesList"></div>
91
- </div>
92
-
93
- <!-- 正向穿透面板 -->
94
- <div class="proxies-section hidden" id="forwardPanel">
95
- <div class="section-header">
96
- <div class="section-title">正向穿透映射</div>
97
- <div>
98
- <button class="btn btn-primary btn-sm forward-btn" id="refreshClients">刷新客户端</button>
99
- <button class="btn btn-primary btn-sm forward-btn" id="showForwardForm">+ 添加穿透</button>
100
- </div>
101
- </div>
102
-
103
- <div class="section-info">
104
- <strong>正向穿透模式</strong> — 连接本地端口到远程客户端的端口,实现设备间的点对点连接。
105
- 本地端口 <code>:本地端口</code> → 中继服务器 → 目标客户端 <code>:目标端口</code>
106
- </div>
107
-
108
- <!-- 客户端注册状态 -->
109
- <div id="registerSection" style="margin-bottom: 16px;">
110
- <button class="btn btn-primary forward-btn btn-sm" id="registerBtn">注册到服务器</button>
111
- <span id="registerStatus" style="margin-left: 12px; color: #888; font-size: 13px;"></span>
112
- </div>
113
-
114
- <!-- 在线客户端列表 -->
115
- <div id="clientsSection" class="hidden" style="margin-bottom: 16px;">
116
- <div class="section-subtitle" style="font-size: 13px; color: #888; margin-bottom: 8px;">在线客户端列表</div>
117
- <div id="clientsList" class="client-list"></div>
118
- </div>
119
-
120
- <div class="add-form forward-form" id="forwardForm">
121
- <div class="form-row forward-row">
122
- <div class="form-group">
123
- <label>本地端口</label>
124
- <input type="number" id="forwardLocalPort" placeholder="8080" min="1" max="65535">
125
- </div>
126
- <div class="form-group">
127
- <label>目标客户端</label>
128
- <select id="targetClientId">
129
- <option value="">请先刷新客户端列表</option>
130
- </select>
131
- </div>
132
- <div class="form-group">
133
- <label>目标端口</label>
134
- <input type="number" id="forwardTargetPort" placeholder="3000" min="1" max="65535">
135
- </div>
136
- <div class="form-actions">
137
- <button class="btn btn-primary forward-btn" id="addForward">添加</button>
138
- <button class="btn btn-secondary" id="cancelForward">取消</button>
139
- </div>
140
- </div>
141
- </div>
142
-
143
- <div id="forwardList"></div>
144
- </div>
145
-
146
- <!-- 使用说明面板 -->
147
- <div class="usage-guide hidden" id="guidePanel">
148
- <div class="usage-title">📖 使用说明</div>
149
-
150
- <div class="usage-section">
151
- <div class="usage-subtitle">反向代理模式</div>
152
- <div class="usage-text">
153
- <strong>用途:</strong>将本地服务暴露到公网,适用于开发调试、远程访问本地服务等场景。<br><br>
154
- <strong>工作原理:</strong><br>
155
- 1. 客户端连接到中继服务器并注册代理映射<br>
156
- 2. 服务器在指定公网端口监听连接<br>
157
- 3. 外部用户访问 <code>http://服务器IP:远程端口</code><br>
158
- 4. 服务器将连接通过数据通道转发到客户端的本地端口<br><br>
159
- <strong>使用场景:</strong>本地开发、微信开发、远程桌面、NAS 访问等
160
- </div>
161
- </div>
162
-
163
- <div class="usage-section">
164
- <div class="usage-subtitle">正向穿透模式</div>
165
- <div class="usage-text">
166
- <strong>用途:</strong>实现多个内网设备之间的点对点连接,无需公网暴露。<br><br>
167
- <strong>工作原理:</strong><br>
168
- 1. 多个客户端都连接到同一个中继服务器<br>
169
- 2. 客户端 A 注册为可被发现,客户端 B 可以查看在线客户端列表<br>
170
- 3. 客户端 B 创建本地端口 → 目标客户端端口的映射<br>
171
- 4. 用户连接本地端口,数据通过中继服务器转发到目标客户端的指定端口<br><br>
172
- <strong>使用场景:</strong>分支机构互联、SSH 跳板机、远程办公室设备访问、点对点文件传输
173
- </div>
174
- </div>
175
-
176
- <div class="usage-section">
177
- <div class="usage-subtitle">架构对比</div>
178
- <div class="usage-text">
179
- <strong>反向代理(传统模式):</strong><br>
180
- 公网用户 → 中继服务器:端口 → 内网客户端:本地端口<br><br>
181
- <strong>典型工具:</strong>ngrok、frp<br><br>
182
-
183
- <strong>正向穿透(本系统特色):</strong><br>
184
- 用户 → 客户端A:本地端口 → 中继服务器 → 客户端B:目标端口<br>
185
- <strong>优势:</strong>无需公网暴露、设备间直连、支持多客户端组网
186
- </div>
187
- </div>
188
-
189
- <div class="usage-section">
190
- <div class="usage-subtitle">快速开始</div>
191
- <div class="usage-text">
192
- <strong>1. 启动服务器:</strong><code>npx @feng3d/cts start</code><br>
193
- <strong>2. 启动客户端 A(反向代理):</strong><code>npx @feng3d/ctc start -p "8080:3000"</code><br>
194
- <strong>3. 启动客户端 B(正向穿透):</strong><code>npx @feng3d/ctc start</code>,然后在管理页面注册并添加穿透映射<br>
195
- <strong>4. 访问服务:</strong>浏览器打开 <code>http://服务器IP:8080</code> 即可访问客户端 A 的本地服务
196
- </div>
197
- </div>
198
- </div>
199
-
200
- <div class="last-update">最后更新: <span id="lastUpdate">-</span></div>
201
-
202
- <div class="footer">
203
- <a href="https://github.com/feng3d/chuantou" target="_blank">feng3d-ctc</a>
204
- — 内网穿透客户端
205
- </div>
206
- </div>
207
-
208
- <div class="modal" id="deleteModal">
209
- <div class="modal-content">
210
- <div class="modal-title">确认删除代理</div>
211
- <div class="modal-body">确定要删除此代理映射吗?</div>
212
- <div class="modal-actions">
213
- <button class="btn btn-secondary" id="cancelDelete">取消</button>
214
- <button class="btn btn-danger" id="confirmDelete">删除</button>
215
- </div>
216
- </div>
217
- </div>
218
-
219
- <div class="toast" id="toast"></div>
220
-
221
- </body>
222
- </html>
@@ -1 +0,0 @@
1
- *{margin:0;padding:0;box-sizing:border-box}body{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;background:linear-gradient(135deg,#1a1a2e,#16213e);min-height:100vh;color:#e0e0e0;padding:20px}.container{max-width:1000px;margin:0 auto}.header{text-align:center;margin-bottom:20px;padding:20px;background:#ffffff0d;border-radius:16px;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.1)}.header-actions{display:flex;align-items:center;justify-content:center;gap:16px}.header h1{font-size:24px;margin-bottom:0;background:linear-gradient(90deg,#00d9ff,#0f8);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}.mode-badge{display:inline-block;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:500;background:#00d9ff26;color:#00d9ff;margin-left:8px}.mode-badge.forward{background:#ff880026;color:#f80}.tabs{display:flex;justify-content:center;gap:8px;margin-bottom:20px}.tab{padding:10px 20px;background:#ffffff0d;border:none;border-radius:8px;color:#888;cursor:pointer;font-size:14px;transition:all .2s;border:1px solid transparent}.tab:hover{background:#ffffff1a}.tab.active{background:#00d9ff26;color:#00d9ff;border-color:#00d9ff4d}.tab.active.forward-tab{background:#ff880026;color:#f80;border-color:#ff88004d}.status{display:inline-flex;align-items:center;gap:8px;padding:6px 16px;border-radius:20px;font-size:14px;font-weight:500}.status.running{background:#00ff8826;color:#0f8}.status.running:before{content:"";width:8px;height:8px;border-radius:50%;background:#0f8;animation:pulse 1.5s infinite}.status.stopped{background:#ff4d4d26;color:#ff4d4d}@keyframes pulse{0%,to{opacity:1}50%{opacity:.4}}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:12px;margin-bottom:20px}.card{background:#ffffff0d;border-radius:12px;padding:16px;border:1px solid rgba(255,255,255,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.card-label{font-size:11px;color:#888;margin-bottom:4px;text-transform:uppercase;letter-spacing:.5px}.card-value{font-size:18px;font-weight:600;color:#fff}.card-value .unit{font-size:12px;color:#888;font-weight:400}.proxies-section{background:#ffffff0d;border-radius:12px;padding:20px;border:1px solid rgba(255,255,255,.1);margin-top:0}.section-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px}.header-buttons{display:flex;gap:8px}.section-title{font-size:13px;color:#888;text-transform:uppercase;letter-spacing:.5px}.section-info{font-size:12px;color:#666;margin-bottom:12px;line-height:1.6}.section-info code{background:#ffffff1a;padding:2px 6px;border-radius:4px;font-family:monospace;color:#00d9ff}.btn{padding:8px 16px;border-radius:8px;border:none;font-size:13px;cursor:pointer;transition:all .2s}.btn-primary{background:linear-gradient(135deg,#00d9ff,#0f8);color:#000;font-weight:500}.btn-primary:hover{opacity:.9;transform:translateY(-1px)}.btn-primary.forward-btn{background:linear-gradient(135deg,#f80,#fa0)}.btn-danger{background:#ff4d4d33;color:#ff4d4d;padding:4px 10px;font-size:12px}.btn-danger:hover{background:#ff4d4d4d}.btn-secondary{background:#ffffff1a;color:#fff}.btn-secondary:hover{background:#ffffff26}.btn-sm{padding:6px 12px;font-size:12px}.proxy-item{display:flex;justify-content:space-between;align-items:center;padding:12px 16px;background:#0003;border-radius:8px;margin-bottom:8px;font-size:14px}.proxy-item:last-child{margin-bottom:0}.proxy-item.forward-item{background:#ff88001a;border-left:3px solid #ff8800}.proxy-info{display:flex;align-items:center;gap:12px;flex:1}.proxy-index{padding:2px 8px;border-radius:4px;font-size:11px;font-weight:600;background:#ffffff1a;color:#aaa;min-width:32px;text-align:center}.proxy-protocol{padding:2px 8px;border-radius:4px;font-size:10px;font-weight:500;text-transform:uppercase;background:#00d9ff33;color:#00d9ff}.proxy-item.forward-item .proxy-protocol{background:#f803;color:#f80}.proxy-remote,.proxy-local{font-family:monospace}.proxy-remote{color:#00d9ff}.proxy-item.forward-item .proxy-remote{color:#f80}.proxy-arrow{color:#666}.proxy-local{color:#888}.proxy-status{padding:4px 10px;border-radius:4px;font-size:14px;font-weight:700;min-width:32px;text-align:center}.proxy-status.status-unknown{background:#8883;color:#888}.proxy-status.status-testing{background:#ffc10733;color:#ffc107;animation:pulse 1s infinite}.proxy-status.status-connected{background:#0f83;color:#0f8}.proxy-status.status-failed{background:#ff453a33;color:#ff453a}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.empty-state{text-align:center;padding:30px;color:#666;font-size:14px}.add-form{display:none;background:#0000004d;border-radius:12px;padding:20px;margin-bottom:16px}.add-form.forward-form{background:#ff88001a;border-left:3px solid #ff8800}.add-form.show{display:block}.form-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr)) auto;gap:12px;align-items:end}.form-row.forward-row{grid-template-columns:1fr 1fr 1fr auto}.form-group{display:flex;flex-direction:column;gap:6px}.form-group label{font-size:11px;color:#888;text-transform:uppercase}.form-group input,.form-group select{padding:10px 12px;background:#ffffff0d;border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#fff;font-size:14px;width:100%}.form-group input:focus,.form-group select:focus{outline:none;border-color:#00d9ff}.form-group select{cursor:pointer}.form-actions{display:flex;gap:8px}.modal{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:#000000b3;align-items:center;justify-content:center;z-index:1000}.modal.show{display:flex}.modal-content{background:#1a1a2e;border-radius:16px;padding:24px;max-width:500px;width:90%;border:1px solid rgba(255,255,255,.1)}.modal-title{font-size:16px;margin-bottom:16px;font-weight:600}.modal-body{color:#888;font-size:14px;line-height:1.6;margin-bottom:16px}.modal-actions{display:flex;gap:12px;margin-top:20px}.modal-actions .btn{flex:1}.last-update{text-align:center;color:#666;font-size:11px;margin-top:20px}.footer{text-align:center;margin-top:30px;padding:20px;color:#666;font-size:12px}.footer a{color:#00d9ff;text-decoration:none}.toast{position:fixed;bottom:20px;right:20px;padding:12px 20px;border-radius:8px;font-size:14px;transform:translateY(100px);opacity:0;transition:all .3s;z-index:2000}.toast.show{transform:translateY(0);opacity:1}.toast.success{background:#0f83;color:#0f8;border:1px solid rgba(0,255,136,.3)}.toast.error{background:#ff4d4d33;color:#ff4d4d;border:1px solid rgba(255,77,77,.3)}.usage-guide{background:#ffffff08;border-radius:12px;padding:20px;margin-top:20px;border:1px solid rgba(255,255,255,.08)}.usage-title{font-size:14px;font-weight:600;margin-bottom:12px;color:#00d9ff}.usage-section{margin-bottom:16px}.usage-section:last-child{margin-bottom:0}.usage-subtitle{font-size:13px;font-weight:500;margin-bottom:8px;color:#e0e0e0}.usage-text{font-size:13px;color:#888;line-height:1.6}.usage-text code{background:#00d9ff26;padding:2px 6px;border-radius:4px;font-family:monospace;color:#00d9ff}.usage-text .forward-code{background:#ff880026;color:#f80}.client-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;margin-bottom:16px}.client-card{background:#ffffff0d;border-radius:8px;padding:12px;border:1px solid rgba(255,255,255,.1);cursor:pointer;transition:all .2s}.client-card:hover{background:#ffffff14;border-color:#ff88004d}.client-card.selected{border-color:#f80;background:#ff88001a}.client-id{font-weight:600;color:#e0e0e0;margin-bottom:4px}.client-desc{font-size:12px;color:#888}.no-clients{text-align:center;padding:20px;color:#666;font-size:13px}.section-subtitle{font-size:13px;color:#888;margin-bottom:8px}.hidden{display:none!important}
@@ -1,149 +0,0 @@
1
- /**
2
- * @module admin-server
3
- * @description 客户端管理页面 HTTP 服务器模块。
4
- * 提供一个本地 HTTP 服务,用于查看客户端状态和管理代理映射。
5
- * 支持反向代理模式和正向穿透模式。
6
- */
7
- import { ProxyConfig, ForwardProxyEntry } from '@feng3d/chuantou-shared';
8
- export type { ForwardProxyEntry };
9
- /**
10
- * 客户端状态信息接口
11
- */
12
- export interface ClientStatus {
13
- /** 是否正在运行 */
14
- running: boolean;
15
- /** 服务器地址 */
16
- serverUrl: string;
17
- /** 是否已连接 */
18
- connected: boolean;
19
- /** 是否已认证 */
20
- authenticated: boolean;
21
- /** 运行时长(毫秒) */
22
- uptime: number;
23
- /** 代理列表(包含注册状态) */
24
- proxies: (ProxyConfig & {
25
- registered?: boolean;
26
- })[];
27
- /** 重连次数 */
28
- reconnectAttempts: number;
29
- /** 正向穿透代理列表 */
30
- forwardProxies?: Array<{
31
- localPort: number;
32
- targetClientId: string;
33
- targetPort: number;
34
- }>;
35
- /** 客户端是否已注册到服务器(正向穿透模式) */
36
- isRegistered?: boolean;
37
- /** 当前客户端ID */
38
- clientId?: string;
39
- }
40
- /**
41
- * 管理页面服务器配置接口
42
- */
43
- export interface AdminServerConfig {
44
- /** 监听端口 */
45
- port: number;
46
- /** 监听地址 */
47
- host: string;
48
- }
49
- /**
50
- * 管理页面服务器类
51
- *
52
- * 在本地启动一个 HTTP 服务器,提供状态查询和代理管理的 API 接口,
53
- * 以及一个可视化的 Web 管理界面。
54
- */
55
- export declare class AdminServer {
56
- /** HTTP 服务器实例 */
57
- private server;
58
- /** 监听端口 */
59
- private port;
60
- /** 监听地址 */
61
- private host;
62
- /** 启动时间 */
63
- private startedAt;
64
- /** 获取状态回调函数 */
65
- private getStatusCallback;
66
- /** 添加代理回调函数 */
67
- private addProxyCallback;
68
- /** 删除代理回调函数 */
69
- private removeProxyCallback;
70
- /** 正向穿透代理列表(用于存储运行时的正向穿透配置) */
71
- private forwardProxies;
72
- /** 发送消息到服务端的回调(用于正向穿透操作) */
73
- private sendMessageCallback?;
74
- /** 触发重连的回调函数 */
75
- private reconnectCallback?;
76
- /** 添加正向穿透代理回调函数 */
77
- private addForwardProxyCallback?;
78
- /** 删除正向穿透代理回调函数 */
79
- private removeForwardProxyCallback?;
80
- /** 注册客户端回调函数 */
81
- private registerClientCallback?;
82
- /** 获取客户端列表回调函数 */
83
- private getClientListCallback?;
84
- /** 关闭进程回调函数 */
85
- private shutdownCallback?;
86
- /**
87
- * 静态文件路径常量
88
- */
89
- private static readonly STATIC_DIR;
90
- /**
91
- * HTML 模板文件路径
92
- */
93
- private static readonly TEMPLATE_PATH;
94
- /**
95
- * 创建管理服务器实例
96
- *
97
- * @param config - 服务器配置
98
- * @param getStatus - 获取状态的回调函数
99
- * @param addProxy - 添加反向代理的回调函数
100
- * @param removeProxy - 删除反向代理的回调函数
101
- * @param addForwardProxy - 添加正向穿透的回调函数
102
- * @param removeForwardProxy - 删除正向穿透的回调函数
103
- * @param registerClient - 注册客户端的回调函数
104
- * @param getClientList - 获取客户端列表的回调函数
105
- * @param reconnect - 触发重连的回调函数
106
- */
107
- constructor(config: AdminServerConfig, getStatus: () => ClientStatus, addProxy: (proxy: ProxyConfig) => Promise<void>, removeProxy: (remotePort: number) => Promise<void>, addForwardProxy?: (entry: ForwardProxyEntry) => Promise<void>, removeForwardProxy?: (localPort: number) => Promise<void>, registerClient?: (description?: string) => Promise<void>, getClientList?: () => Promise<any>, sendMessage?: (message: any) => Promise<any>, reconnect?: () => Promise<void>);
108
- /**
109
- * 设置发送消息的回调
110
- */
111
- setSendMessageCallback(callback: (message: any) => Promise<any>): void;
112
- /**
113
- * 设置重连回调
114
- */
115
- setReconnectCallback(callback: () => Promise<void>): void;
116
- /**
117
- * 设置关闭进程回调
118
- */
119
- setShutdownCallback(callback: () => Promise<void>): void;
120
- /**
121
- * 启动服务器
122
- *
123
- * @returns 启动完成的 Promise
124
- */
125
- start(): Promise<void>;
126
- /**
127
- * 处理 HTTP 请求
128
- *
129
- * 提供以下端点:
130
- * 反向代理:
131
- * - `GET /` - 管理页面
132
- * - `GET /_ctc/status` - 获取状态
133
- * - `POST /_ctc/proxies` - 添加反向代理
134
- * - `DELETE /_ctc/proxies/:port` - 删除反向代理
135
- * - `POST /_ctc/test-proxy?port=xxx` - 测试反向代理连接
136
- * 正向穿透:
137
- * - `GET /_ctc/forward/list` - 获取正向穿透列表
138
- * - `POST /_ctc/forward/add` - 添加正向穿透
139
- * - `POST /_ctc/forward/remove` - 删除正向穿透
140
- * - `GET /_ctc/forward/clients` - 获取客户端列表
141
- * - `POST /_ctc/forward/register` - 注册到服务器
142
- */
143
- private handleRequest;
144
- /**
145
- * 停止服务器
146
- */
147
- stop(): Promise<void>;
148
- }
149
- //# sourceMappingURL=admin-server.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin-server.d.ts","sourceRoot":"","sources":["../src/admin-server.ts"],"names":[],"mappings":"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"}