sonx 0.2.0__tar.gz → 0.2.1__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.
- {sonx-0.2.0 → sonx-0.2.1}/PKG-INFO +1 -1
- {sonx-0.2.0 → sonx-0.2.1}/pyproject.toml +4 -1
- sonx-0.2.1/son/web/static/index.html +374 -0
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/PKG-INFO +1 -1
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/SOURCES.txt +1 -0
- {sonx-0.2.0 → sonx-0.2.1}/README.md +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/setup.cfg +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/__main__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/connect.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/discover.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/main.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/tunnel.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/cli/web.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/client/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/client/connector.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/client/discovery.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/client/tunnel.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/core/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/core/protocol.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/core/types.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/broadcast.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/input.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/input_stub.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/input_win.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/main.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/server.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/daemon/shell.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/web/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/son/web/server.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sond/__init__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sond/__main__.py +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/dependency_links.txt +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/entry_points.txt +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/requires.txt +0 -0
- {sonx-0.2.0 → sonx-0.2.1}/sonx.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "sonx"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "Superior Organised Network - Remote access tool"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -44,3 +44,6 @@ build-backend = "setuptools.build_meta"
|
|
|
44
44
|
|
|
45
45
|
[tool.setuptools.packages.find]
|
|
46
46
|
include = ["son*", "sond*"]
|
|
47
|
+
|
|
48
|
+
[tool.setuptools.package-data]
|
|
49
|
+
son = ["web/static/*"]
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>son — remote access</title>
|
|
7
|
+
<link href="https://unpkg.com/xterm@5/css/xterm.css" rel="stylesheet">
|
|
8
|
+
<style>
|
|
9
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
10
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #1a1a2e; color: #e0e0e0; height: 100vh; display: flex; flex-direction: column; }
|
|
11
|
+
header { background: #16213e; padding: 12px 20px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #0f3460; }
|
|
12
|
+
header h1 { font-size: 18px; font-weight: 600; color: #e94560; }
|
|
13
|
+
header h1 span { color: #eee; font-weight: 300; }
|
|
14
|
+
#main { display: flex; flex: 1; overflow: hidden; }
|
|
15
|
+
#sidebar { width: 280px; background: #16213e; padding: 12px; display: flex; flex-direction: column; gap: 8px; border-right: 1px solid #0f3460; overflow-y: auto; }
|
|
16
|
+
#sidebar h2 { font-size: 13px; text-transform: uppercase; letter-spacing: 1px; color: #888; margin-bottom: 4px; }
|
|
17
|
+
#panel { flex: 1; display: flex; flex-direction: column; background: #000; }
|
|
18
|
+
#mode-bar { display: flex; background: #0f3460; border-bottom: 1px solid #1a5276; }
|
|
19
|
+
#mode-bar button { background: none; border: none; color: #888; padding: 8px 20px; font-size: 13px; cursor: pointer; border-bottom: 2px solid transparent; transition: all .15s; }
|
|
20
|
+
#mode-bar button:hover { color: #e0e0e0; }
|
|
21
|
+
#mode-bar button.active { color: #e94560; border-bottom-color: #e94560; }
|
|
22
|
+
#terminal-container { flex: 1; display: flex; }
|
|
23
|
+
#terminal { flex: 1; }
|
|
24
|
+
#desktop-container { flex: 1; display: none; position: relative; overflow: hidden; cursor: crosshair; }
|
|
25
|
+
#desktop-container.active { display: flex; align-items: center; justify-content: center; }
|
|
26
|
+
#desktop-canvas { display: block; background: #111; image-rendering: pixelated; }
|
|
27
|
+
#controls { padding: 8px 12px; background: #16213e; display: flex; gap: 8px; align-items: center; border-top: 1px solid #0f3460; flex-wrap: wrap; }
|
|
28
|
+
#controls input, #controls select { background: #0f3460; border: 1px solid #1a5276; color: #e0e0e0; padding: 6px 10px; border-radius: 4px; font-size: 13px; }
|
|
29
|
+
#controls button { background: #e94560; color: #fff; border: none; padding: 6px 14px; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; }
|
|
30
|
+
#controls button:hover { background: #c73652; }
|
|
31
|
+
#controls button:disabled { opacity: .5; cursor: default; }
|
|
32
|
+
#status { font-size: 12px; color: #888; margin-left: auto; }
|
|
33
|
+
.sond-entry { background: #0f3460; border-radius: 4px; padding: 8px 10px; cursor: pointer; transition: background .15s; }
|
|
34
|
+
.sond-entry:hover { background: #1a5276; }
|
|
35
|
+
.sond-entry .name { font-weight: 500; font-size: 14px; }
|
|
36
|
+
.sond-entry .addr { font-size: 11px; color: #888; margin-top: 2px; }
|
|
37
|
+
.sond-entry .badge { display: inline-block; font-size: 10px; padding: 1px 6px; border-radius: 3px; margin-top: 4px; background: #1a5276; color: #aaa; }
|
|
38
|
+
#desktop-hint { position: absolute; bottom: 12px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,.7); color: #888; padding: 6px 14px; border-radius: 4px; font-size: 12px; pointer-events: none; white-space: nowrap; }
|
|
39
|
+
</style>
|
|
40
|
+
</head>
|
|
41
|
+
<body>
|
|
42
|
+
<header>
|
|
43
|
+
<h1>son<span>net</span></h1>
|
|
44
|
+
<span style="font-size:12px;color:#888;flex:1">remote access terminal</span>
|
|
45
|
+
</header>
|
|
46
|
+
<div id="main">
|
|
47
|
+
<div id="sidebar">
|
|
48
|
+
<h2>discovered</h2>
|
|
49
|
+
<div id="discover-list">
|
|
50
|
+
<div style="font-size:12px;color:#666;padding:8px">scanning...</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
<div id="panel">
|
|
54
|
+
<div id="mode-bar">
|
|
55
|
+
<button id="mode-terminal" class="active" onclick="switchMode('terminal')">Terminal</button>
|
|
56
|
+
<button id="mode-desktop" onclick="switchMode('desktop')">Desktop</button>
|
|
57
|
+
</div>
|
|
58
|
+
<div id="terminal-container">
|
|
59
|
+
<div id="terminal"></div>
|
|
60
|
+
</div>
|
|
61
|
+
<div id="desktop-container">
|
|
62
|
+
<canvas id="desktop-canvas"></canvas>
|
|
63
|
+
<div id="desktop-hint">click to capture keyboard · Ctrl+Alt+Del to release</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div id="controls">
|
|
66
|
+
<input type="text" id="host-input" placeholder="host or IP" value="127.0.0.1">
|
|
67
|
+
<input type="number" id="port-input" value="9443" style="width:70px" placeholder="port">
|
|
68
|
+
<input type="password" id="password-input" placeholder="password (optional)" style="width:120px">
|
|
69
|
+
<select id="shell-select">
|
|
70
|
+
<option value="">auto</option>
|
|
71
|
+
<option value="powershell">powershell</option>
|
|
72
|
+
<option value="cmd">cmd</option>
|
|
73
|
+
<option value="bash">bash</option>
|
|
74
|
+
</select>
|
|
75
|
+
<button id="connect-btn" onclick="connect()">connect</button>
|
|
76
|
+
<button id="disconnect-btn" onclick="disconnect()" disabled>disconnect</button>
|
|
77
|
+
<span id="status">idle</span>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<script src="https://unpkg.com/xterm@5/lib/xterm.js"></script>
|
|
83
|
+
<script>
|
|
84
|
+
const term = new Terminal({
|
|
85
|
+
cursorBlink: true,
|
|
86
|
+
cursorStyle: 'block',
|
|
87
|
+
fontSize: 14,
|
|
88
|
+
fontFamily: 'Menlo, Monaco, "Cascadia Code", Consolas, monospace',
|
|
89
|
+
theme: { background: '#000', foreground: '#e0e0e0', cursor: '#e94560' },
|
|
90
|
+
cols: 100,
|
|
91
|
+
rows: 30,
|
|
92
|
+
});
|
|
93
|
+
term.open(document.getElementById('terminal'));
|
|
94
|
+
term.focus();
|
|
95
|
+
|
|
96
|
+
let ws = null;
|
|
97
|
+
let desktopWs = null;
|
|
98
|
+
let activeHost = '';
|
|
99
|
+
let currentMode = 'terminal';
|
|
100
|
+
let desktopCapture = false;
|
|
101
|
+
let screenW = 1920;
|
|
102
|
+
let screenH = 1080;
|
|
103
|
+
|
|
104
|
+
const VK = {
|
|
105
|
+
// Letters
|
|
106
|
+
A:0x41,B:0x42,C:0x43,D:0x44,E:0x45,F:0x46,G:0x47,H:0x48,
|
|
107
|
+
I:0x49,J:0x4A,K:0x4B,L:0x4C,M:0x4D,N:0x4E,O:0x4F,
|
|
108
|
+
P:0x50,Q:0x51,R:0x52,S:0x53,T:0x54,U:0x55,V:0x56,
|
|
109
|
+
W:0x57,X:0x58,Y:0x59,Z:0x5A,
|
|
110
|
+
// Digits
|
|
111
|
+
D0:0x30,D1:0x31,D2:0x32,D3:0x33,D4:0x34,D5:0x35,D6:0x36,D7:0x37,D8:0x38,D9:0x39,
|
|
112
|
+
// Numpad
|
|
113
|
+
Numpad0:0x60,Numpad1:0x61,Numpad2:0x62,Numpad3:0x63,Numpad4:0x64,
|
|
114
|
+
Numpad5:0x65,Numpad6:0x66,Numpad7:0x67,Numpad8:0x68,Numpad9:0x69,
|
|
115
|
+
NumpadMultiply:0x6A,NumpadAdd:0x6B,NumpadSubtract:0x6D,
|
|
116
|
+
NumpadDecimal:0x6E,NumpadDivide:0x6F,
|
|
117
|
+
// Function
|
|
118
|
+
F1:0x70,F2:0x71,F3:0x72,F4:0x73,F5:0x74,F6:0x75,
|
|
119
|
+
F7:0x76,F8:0x77,F9:0x78,F10:0x79,F11:0x7A,F12:0x7B,
|
|
120
|
+
F13:0x7C,F14:0x7D,F15:0x7E,F16:0x7F,F17:0x80,F18:0x81,
|
|
121
|
+
F19:0x82,F20:0x83,F21:0x84,F22:0x85,F23:0x86,F24:0x87,
|
|
122
|
+
// Modifiers
|
|
123
|
+
ShiftLeft:0xA0,ShiftRight:0xA1,
|
|
124
|
+
ControlLeft:0xA2,ControlRight:0xA3,
|
|
125
|
+
AltLeft:0xA4,AltRight:0xA5,
|
|
126
|
+
MetaLeft:0x5B,MetaRight:0x5C, // Win keys
|
|
127
|
+
// Navigation
|
|
128
|
+
ArrowUp:0x26,ArrowDown:0x28,ArrowLeft:0x25,ArrowRight:0x27,
|
|
129
|
+
Home:0x24,End:0x23,PageUp:0x21,PageDown:0x22,
|
|
130
|
+
Insert:0x2D,Delete:0x2E,
|
|
131
|
+
// Editing
|
|
132
|
+
Backspace:0x08,Tab:0x09,Enter:0x0D,Escape:0x1B,Space:0x20,
|
|
133
|
+
CapsLock:0x14,NumLock:0x90,ScrollLock:0x91,
|
|
134
|
+
Pause:0x13,PrintScreen:0x2C,
|
|
135
|
+
// Symbols
|
|
136
|
+
Minus:0xBD,Equal:0xBB,BracketLeft:0xDB,BracketRight:0xDD,
|
|
137
|
+
Semicolon:0xBA,Quote:0xDE,Comma:0xBC,Period:0xBE,Slash:0xBF,
|
|
138
|
+
Backquote:0xC0,Backslash:0xDC,
|
|
139
|
+
// Numpad enter
|
|
140
|
+
NumpadEnter:0x0D,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const EXTENDED_KEYS = new Set([
|
|
144
|
+
'ArrowUp','ArrowDown','ArrowLeft','ArrowRight',
|
|
145
|
+
'Home','End','PageUp','PageDown',
|
|
146
|
+
'Insert','Delete',
|
|
147
|
+
'ControlRight','AltRight','MetaRight',
|
|
148
|
+
'NumpadEnter',
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
function log(msg) {
|
|
152
|
+
term.writeln(`\x1b[90m[${new Date().toLocaleTimeString()}] ${msg}\x1b[0m`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function setStatus(s) { document.getElementById('status').textContent = s; }
|
|
156
|
+
|
|
157
|
+
function switchMode(mode) {
|
|
158
|
+
currentMode = mode;
|
|
159
|
+
document.getElementById('mode-terminal').className = mode === 'terminal' ? 'active' : '';
|
|
160
|
+
document.getElementById('mode-desktop').className = mode === 'desktop' ? 'active' : '';
|
|
161
|
+
document.getElementById('terminal-container').style.display = mode === 'terminal' ? 'flex' : 'none';
|
|
162
|
+
document.getElementById('desktop-container').className = mode === 'desktop' ? 'active' : '';
|
|
163
|
+
if (mode === 'terminal') {
|
|
164
|
+
term.focus();
|
|
165
|
+
if (desktopCapture) releaseDesktopCapture();
|
|
166
|
+
} else {
|
|
167
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
168
|
+
connectDesktop();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function connectDesktop() {
|
|
174
|
+
if (desktopWs) return;
|
|
175
|
+
const host = document.getElementById('host-input').value.trim();
|
|
176
|
+
const port = parseInt(document.getElementById('port-input').value) || 9443;
|
|
177
|
+
const password = document.getElementById('password-input').value;
|
|
178
|
+
const params = new URLSearchParams({ host, port, password });
|
|
179
|
+
desktopWs = new WebSocket(`ws://${location.host}/ws/desktop?${params}`);
|
|
180
|
+
desktopWs.onopen = () => {
|
|
181
|
+
log('desktop session connected');
|
|
182
|
+
desktopWs.send(JSON.stringify({ type: 'desktop_meta' }));
|
|
183
|
+
};
|
|
184
|
+
desktopWs.onmessage = (e) => {
|
|
185
|
+
const msg = JSON.parse(e.data);
|
|
186
|
+
if (msg.type === 'desktop_meta') {
|
|
187
|
+
screenW = msg.screen_width;
|
|
188
|
+
screenH = msg.screen_height;
|
|
189
|
+
resizeCanvas();
|
|
190
|
+
log(`remote screen: ${screenW}x${screenH}`);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
desktopWs.onclose = () => {
|
|
194
|
+
if (desktopCapture) releaseDesktopCapture();
|
|
195
|
+
desktopWs = null;
|
|
196
|
+
log('desktop session closed');
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function resizeCanvas() {
|
|
201
|
+
const c = document.getElementById('desktop-canvas');
|
|
202
|
+
const container = document.getElementById('desktop-container');
|
|
203
|
+
const maxW = container.clientWidth - 40;
|
|
204
|
+
const maxH = container.clientHeight - 60;
|
|
205
|
+
const scale = Math.min(maxW / screenW, maxH / screenH, 1);
|
|
206
|
+
c.width = screenW;
|
|
207
|
+
c.height = screenH;
|
|
208
|
+
c.style.width = (screenW * scale) + 'px';
|
|
209
|
+
c.style.height = (screenH * scale) + 'px';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function captureDesktopKeyboard() {
|
|
213
|
+
desktopCapture = true;
|
|
214
|
+
document.getElementById('desktop-hint').style.display = 'none';
|
|
215
|
+
document.addEventListener('keydown', desktopKeyHandler);
|
|
216
|
+
document.addEventListener('keyup', desktopKeyHandler);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function releaseDesktopCapture() {
|
|
220
|
+
desktopCapture = false;
|
|
221
|
+
document.getElementById('desktop-hint').style.display = '';
|
|
222
|
+
document.removeEventListener('keydown', desktopKeyHandler);
|
|
223
|
+
document.removeEventListener('keyup', desktopKeyHandler);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function desktopKeyHandler(e) {
|
|
227
|
+
const vk = VK[e.code];
|
|
228
|
+
if (!vk) return;
|
|
229
|
+
if (desktopWs && desktopWs.readyState === WebSocket.OPEN) {
|
|
230
|
+
e.preventDefault();
|
|
231
|
+
const isDown = e.type === 'keydown';
|
|
232
|
+
const extended = EXTENDED_KEYS.has(e.code);
|
|
233
|
+
desktopWs.send(JSON.stringify({
|
|
234
|
+
type: 'keyboard', vk, down: isDown, extended,
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
// Release capture on Ctrl+Alt+Del
|
|
238
|
+
if (e.type === 'keydown' && e.ctrlKey && e.altKey && e.code === 'Delete') {
|
|
239
|
+
releaseDesktopCapture();
|
|
240
|
+
switchMode('terminal');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const canvas = document.getElementById('desktop-canvas');
|
|
245
|
+
canvas.addEventListener('mousemove', (e) => {
|
|
246
|
+
if (!desktopWs || desktopWs.readyState !== WebSocket.OPEN) return;
|
|
247
|
+
const rect = canvas.getBoundingClientRect();
|
|
248
|
+
const scaleX = screenW / rect.width;
|
|
249
|
+
const scaleY = screenH / rect.height;
|
|
250
|
+
const x = Math.round((e.clientX - rect.left) * scaleX);
|
|
251
|
+
const y = Math.round((e.clientY - rect.top) * scaleY);
|
|
252
|
+
desktopWs.send(JSON.stringify({ type: 'mouse_move', x, y }));
|
|
253
|
+
});
|
|
254
|
+
canvas.addEventListener('mousedown', (e) => {
|
|
255
|
+
if (!desktopWs || desktopWs.readyState !== WebSocket.OPEN) return;
|
|
256
|
+
if (!desktopCapture) captureDesktopKeyboard();
|
|
257
|
+
const btn = {0:1,2:2,1:3}[e.button] || 1;
|
|
258
|
+
desktopWs.send(JSON.stringify({ type: 'mouse_click', button: btn, down: true }));
|
|
259
|
+
});
|
|
260
|
+
canvas.addEventListener('mouseup', (e) => {
|
|
261
|
+
if (!desktopWs || desktopWs.readyState !== WebSocket.OPEN) return;
|
|
262
|
+
const btn = {0:1,2:2,1:3}[e.button] || 1;
|
|
263
|
+
desktopWs.send(JSON.stringify({ type: 'mouse_click', button: btn, down: false }));
|
|
264
|
+
});
|
|
265
|
+
canvas.addEventListener('wheel', (e) => {
|
|
266
|
+
if (!desktopWs || desktopWs.readyState !== WebSocket.OPEN) return;
|
|
267
|
+
e.preventDefault();
|
|
268
|
+
desktopWs.send(JSON.stringify({ type: 'mouse_scroll', delta: e.deltaY > 0 ? -120 : 120 }));
|
|
269
|
+
}, { passive: false });
|
|
270
|
+
canvas.addEventListener('click', () => {
|
|
271
|
+
if (!desktopCapture) captureDesktopKeyboard();
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
async function loadSondList() {
|
|
275
|
+
const el = document.getElementById('discover-list');
|
|
276
|
+
try {
|
|
277
|
+
const r = await fetch('/api/discover');
|
|
278
|
+
if (!r.ok) throw new Error('not available');
|
|
279
|
+
const data = await r.json();
|
|
280
|
+
el.innerHTML = '';
|
|
281
|
+
if (!data.length) {
|
|
282
|
+
el.innerHTML = '<div style="font-size:12px;color:#666;padding:8px">no sond instances found</div>';
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
data.forEach(s => {
|
|
286
|
+
const d = document.createElement('div');
|
|
287
|
+
d.className = 'sond-entry';
|
|
288
|
+
d.innerHTML = `<div class="name">${s.hostname}</div><div class="addr">${s.ip}:${s.port}</div><span class="badge">${s.os}</span>`;
|
|
289
|
+
d.onclick = () => {
|
|
290
|
+
document.getElementById('host-input').value = s.ip;
|
|
291
|
+
document.getElementById('port-input').value = s.port;
|
|
292
|
+
if (currentMode === 'desktop') {
|
|
293
|
+
connect();
|
|
294
|
+
} else {
|
|
295
|
+
connect();
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
el.appendChild(d);
|
|
299
|
+
});
|
|
300
|
+
} catch {
|
|
301
|
+
el.innerHTML = '<div style="font-size:12px;color:#666;padding:8px">scanning not available via API</div>';
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function connect() {
|
|
306
|
+
if (ws) disconnect();
|
|
307
|
+
const host = document.getElementById('host-input').value.trim();
|
|
308
|
+
const port = parseInt(document.getElementById('port-input').value) || 9443;
|
|
309
|
+
const password = document.getElementById('password-input').value;
|
|
310
|
+
const shell = document.getElementById('shell-select').value;
|
|
311
|
+
if (!host) { log('enter a host'); return; }
|
|
312
|
+
activeHost = `${host}:${port}`;
|
|
313
|
+
setStatus('connecting...');
|
|
314
|
+
document.getElementById('connect-btn').disabled = true;
|
|
315
|
+
document.getElementById('disconnect-btn').disabled = false;
|
|
316
|
+
term.reset();
|
|
317
|
+
log(`connecting to ${activeHost}...`);
|
|
318
|
+
|
|
319
|
+
const params = new URLSearchParams({ host, port, password, shell });
|
|
320
|
+
ws = new WebSocket(`ws://${location.host}/ws/terminal?${params}`);
|
|
321
|
+
ws.onopen = () => {
|
|
322
|
+
setStatus(`connected to ${activeHost}`);
|
|
323
|
+
log('connected');
|
|
324
|
+
if (currentMode === 'desktop') connectDesktop();
|
|
325
|
+
};
|
|
326
|
+
ws.onmessage = (e) => {
|
|
327
|
+
const msg = JSON.parse(e.data);
|
|
328
|
+
if (msg.type === 'output') term.write(msg.data);
|
|
329
|
+
};
|
|
330
|
+
ws.onclose = () => {
|
|
331
|
+
if (ws) {
|
|
332
|
+
setStatus('disconnected');
|
|
333
|
+
log('disconnected');
|
|
334
|
+
ws = null;
|
|
335
|
+
document.getElementById('connect-btn').disabled = false;
|
|
336
|
+
document.getElementById('disconnect-btn').disabled = true;
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
ws.onerror = () => { log('connection error'); setStatus('error'); document.getElementById('connect-btn').disabled = false; document.getElementById('disconnect-btn').disabled = true; };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function disconnect() {
|
|
343
|
+
if (desktopWs) { desktopWs.close(); desktopWs = null; }
|
|
344
|
+
if (desktopCapture) releaseDesktopCapture();
|
|
345
|
+
if (ws) { ws.send(JSON.stringify({ type: 'close' })); ws.close(); ws = null; }
|
|
346
|
+
document.getElementById('connect-btn').disabled = false;
|
|
347
|
+
document.getElementById('disconnect-btn').disabled = true;
|
|
348
|
+
setStatus('disconnected');
|
|
349
|
+
log('disconnected');
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
term.onData(data => {
|
|
353
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
354
|
+
ws.send(JSON.stringify({ type: 'input', data }));
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
term.onResize(({ cols, rows }) => {
|
|
358
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
359
|
+
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
document.addEventListener('keydown', e => {
|
|
363
|
+
if (e.key === 'Enter' && !ws && currentMode === 'terminal') {
|
|
364
|
+
const active = document.activeElement;
|
|
365
|
+
if (!active || active.tagName !== 'INPUT') connect();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
window.addEventListener('resize', () => { if (currentMode === 'desktop') resizeCanvas(); });
|
|
370
|
+
|
|
371
|
+
loadSondList();
|
|
372
|
+
</script>
|
|
373
|
+
</body>
|
|
374
|
+
</html>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|