webssh2_client 0.2.31-alpha.3 → 1.0.0-alpha.1
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.
- package/README.md +26 -7
- package/client/index.js +4 -8
- package/client/public/client.htm +98 -86
- package/client/public/webssh2.bundle.js +2 -2
- package/client/public/webssh2.css +3 -11
- package/index.js +16 -30
- package/package.json +33 -16
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# WebSSH2 Client - Web SSH Client
|
|
2
2
|
|
|
3
|
+
[](https://github.com/billchurch/webssh2_client/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/billchurch/webssh2_client/actions/workflows/release.yml)
|
|
5
|
+
|
|
3
6
|

|
|
4
7
|
|
|
5
8
|
WebSSH2 Client is an HTML5 web-based terminal emulator and SSH client component. It uses WebSockets to communicate with a WebSSH2 server, which in turn uses SSH2 to connect to SSH servers.
|
|
@@ -7,9 +10,11 @@ WebSSH2 Client is an HTML5 web-based terminal emulator and SSH client component.
|
|
|
7
10
|

|
|
8
11
|
|
|
9
12
|
# Important Notice
|
|
13
|
+
|
|
10
14
|
This package contains only the browser-side client component of WebSSH2. It requires a compatible WebSSH2 server to function. The server component is available at [webssh2 server](https://github.com/billchurch/webssh2/tree/bigip-server). This package is intended for advanced users who want to customize or integrate the client component independently.
|
|
11
15
|
|
|
12
16
|
# Status
|
|
17
|
+
|
|
13
18
|
This is an experimental refactor of the WebSSH2 v0.2.x client to function as a standalone component. It has been separated from the server-side code to facilitate customization and integration with different frameworks.
|
|
14
19
|
|
|
15
20
|
## Requirements
|
|
@@ -21,17 +26,20 @@ This is an experimental refactor of the WebSSH2 v0.2.x client to function as a s
|
|
|
21
26
|
## Installation
|
|
22
27
|
|
|
23
28
|
1. Clone the repository:
|
|
29
|
+
|
|
24
30
|
```
|
|
25
31
|
git clone https://github.com/billchurch/webssh2_client.git
|
|
26
32
|
cd webssh2_client
|
|
27
33
|
```
|
|
28
34
|
|
|
29
35
|
2. Install dependencies:
|
|
36
|
+
|
|
30
37
|
```
|
|
31
38
|
npm install
|
|
32
39
|
```
|
|
33
40
|
|
|
34
41
|
3. Build the client:
|
|
42
|
+
|
|
35
43
|
```
|
|
36
44
|
npm run build
|
|
37
45
|
```
|
|
@@ -65,6 +73,15 @@ For server setup instructions, refer to the [WebSSH2 server documentation](https
|
|
|
65
73
|
- Multi-factor authentication support (when supported by server)
|
|
66
74
|
- Support for credential replay and reauthentication
|
|
67
75
|
|
|
76
|
+
## Security and Lint Rules
|
|
77
|
+
|
|
78
|
+
- No innerHTML: The client never uses `innerHTML` for user content. All text uses `textContent` and safe DOM building helpers.
|
|
79
|
+
- CSP: Strict `script-src 'self'` (no inline scripts). Inline styles allowed for xterm DOM renderer and safe color updates.
|
|
80
|
+
- ESLint guardrails:
|
|
81
|
+
- `no-unsanitized` plugin blocks unsanitized DOM sinks (`innerHTML`, `outerHTML`, `insertAdjacentHTML`, `document.write`).
|
|
82
|
+
- Additional bans via `no-restricted-properties` for those sinks, and `no-restricted-syntax` for string-based timers and `new Function`.
|
|
83
|
+
- Xterm integration: Terminal output is rendered with `xterm.write()`; no HTML rendering of remote data.
|
|
84
|
+
|
|
68
85
|
## Configuration
|
|
69
86
|
|
|
70
87
|
The client can be configured through:
|
|
@@ -90,12 +107,12 @@ You can configure the client by setting `window.webssh2Config`:
|
|
|
90
107
|
```javascript
|
|
91
108
|
window.webssh2Config = {
|
|
92
109
|
socket: {
|
|
93
|
-
url: null,
|
|
94
|
-
path: '/ssh/socket.io'
|
|
110
|
+
url: null, // WebSocket URL (auto-detected if null)
|
|
111
|
+
path: '/ssh/socket.io' // Socket.IO path
|
|
95
112
|
},
|
|
96
113
|
ssh: {
|
|
97
|
-
host: null,
|
|
98
|
-
port: 22,
|
|
114
|
+
host: null, // SSH server hostname
|
|
115
|
+
port: 22, // SSH server port
|
|
99
116
|
username: null,
|
|
100
117
|
sshterm: 'xterm-color'
|
|
101
118
|
},
|
|
@@ -104,12 +121,12 @@ window.webssh2Config = {
|
|
|
104
121
|
background: 'green'
|
|
105
122
|
},
|
|
106
123
|
autoConnect: false
|
|
107
|
-
}
|
|
124
|
+
}
|
|
108
125
|
```
|
|
109
126
|
|
|
110
127
|
## Development
|
|
111
128
|
|
|
112
|
-
|
|
129
|
+
See [DEVELOPMENT.md](./DEVELOPMENT.md).
|
|
113
130
|
|
|
114
131
|
## Support
|
|
115
132
|
|
|
@@ -125,4 +142,6 @@ This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md
|
|
|
125
142
|
|
|
126
143
|
- [Xterm.js](https://xtermjs.org/) for terminal emulation
|
|
127
144
|
- [Socket.IO](https://socket.io/) for WebSocket communication
|
|
128
|
-
- [
|
|
145
|
+
- [Vite](https://vitejs.dev/) for development and bundling
|
|
146
|
+
- [ESLint](https://eslint.org/) + [Prettier](https://prettier.io/) for code quality
|
|
147
|
+
- [lucide-static](https://github.com/lucide-icons/lucide) for SVG icons
|
package/client/index.js
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
// client
|
|
2
|
-
// client/index.
|
|
2
|
+
// client/index.ts
|
|
3
3
|
import path from 'path';
|
|
4
4
|
import { readFileSync } from 'fs';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
-
|
|
7
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
7
|
const __dirname = path.dirname(__filename);
|
|
9
|
-
|
|
10
|
-
// Read package.json synchronously for version
|
|
11
8
|
const packageJson = JSON.parse(readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
12
|
-
|
|
13
9
|
export default {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
};
|
|
10
|
+
getPublicPath: () => path.join(__dirname, 'public'),
|
|
11
|
+
version: packageJson.version
|
|
12
|
+
};
|
package/client/public/client.htm
CHANGED
|
@@ -1,136 +1,148 @@
|
|
|
1
|
-
<!-- Version Version 0.
|
|
1
|
+
<!-- Version Version 1.0.0-alpha.1 - 2025-09-05T15:41:27.852Z - 200680b -->
|
|
2
2
|
<!-- webssh2-client -->
|
|
3
3
|
<!-- /client/src/client.htm -->
|
|
4
4
|
<!DOCTYPE html>
|
|
5
|
-
<html>
|
|
5
|
+
<html lang="en" class="h-dvh">
|
|
6
6
|
<head>
|
|
7
7
|
<title>WebSSH2</title>
|
|
8
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
8
9
|
<link rel="icon" type="image/x-icon" href="./favicon.ico">
|
|
9
|
-
<script>
|
|
10
|
-
window.webssh2Config = null;
|
|
11
|
-
</script>
|
|
12
10
|
<script type="module" crossorigin src="./webssh2.bundle.js"></script>
|
|
13
11
|
<link rel="stylesheet" crossorigin href="./webssh2.css">
|
|
14
12
|
</head>
|
|
15
|
-
<body>
|
|
13
|
+
<body class="h-dvh overflow-hidden bg-black text-neutral-100">
|
|
16
14
|
<dialog id="loginDialog" class="modal">
|
|
17
|
-
<div class="modal-content">
|
|
18
|
-
<h2>WebSSH2 Login</h2>
|
|
19
|
-
<form id="loginForm" class="
|
|
20
|
-
<
|
|
21
|
-
<input type="text" id="
|
|
22
|
-
<
|
|
23
|
-
|
|
15
|
+
<div class="modal-content relative bg-white text-slate-800 border border-neutral-300 rounded-md shadow-md p-6 w-80 sm:w-[28rem]">
|
|
16
|
+
<h2 class="text-lg font-semibold text-slate-900 mb-2">WebSSH2 Login</h2>
|
|
17
|
+
<form id="loginForm" class="space-y-3">
|
|
18
|
+
<label for="hostInput" class="sr-only">Host</label>
|
|
19
|
+
<input type="text" id="hostInput" name="host" placeholder="Host" required autocomplete="off" autocapitalize="off" spellcheck="false" enterkeyhint="next" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
20
|
+
<label for="portInput" class="sr-only">Port</label>
|
|
21
|
+
<input type="text" id="portInput" name="port" placeholder="Port" value="22" autocomplete="off" autocapitalize="off" spellcheck="false" enterkeyhint="next" inputmode="numeric" pattern="[0-9]*" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
22
|
+
<label for="usernameInput" class="sr-only">Username</label>
|
|
23
|
+
<input type="text" id="usernameInput" name="username" placeholder="Username" required autocomplete="username" autocapitalize="off" spellcheck="false" enterkeyhint="next" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
24
|
+
|
|
24
25
|
<!-- Password section - always visible -->
|
|
25
|
-
<div class="password-wrapper">
|
|
26
|
-
<
|
|
27
|
-
<
|
|
26
|
+
<div class="password-wrapper relative w-full">
|
|
27
|
+
<label for="passwordInput" class="sr-only">Password</label>
|
|
28
|
+
<input type="password" id="passwordInput" name="password" placeholder="Password" autocomplete="current-password" autocapitalize="off" spellcheck="false" enterkeyhint="go" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
29
|
+
<span id="capsLockIcon" class="hidden absolute right-2 top-1/2 -translate-y-1/2 text-red-500 pointer-events-none">⇪</span>
|
|
28
30
|
</div>
|
|
29
|
-
|
|
30
|
-
<!--
|
|
31
|
-
<div class="
|
|
32
|
-
<
|
|
33
|
-
<
|
|
34
|
-
|
|
31
|
+
|
|
32
|
+
<!-- Options row: Add SSH Key + Options -->
|
|
33
|
+
<div class="flex items-center justify-between gap-2">
|
|
34
|
+
<div class="private-key-toggle">
|
|
35
|
+
<button type="button" id="privateKeyToggle" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-slate-600 text-white hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 disabled:opacity-50 disabled:pointer-events-none shadow-sm">
|
|
36
|
+
<i data-icon="key" class="icon-fw"></i> Add SSH Key
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<button type="button" id="loginSettingsBtn" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-slate-700 text-white hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 disabled:opacity-50 disabled:pointer-events-none shadow-sm" aria-label="Options" title="Options">
|
|
41
|
+
<i data-icon="settings" class="icon-fw"></i> Options
|
|
42
|
+
</button>
|
|
43
|
+
</div>
|
|
35
44
|
</div>
|
|
36
|
-
|
|
45
|
+
|
|
37
46
|
<!-- Private key section (initially hidden) -->
|
|
38
47
|
<div id="privateKeySection" class="hidden">
|
|
39
|
-
<div class="private-key-input">
|
|
40
|
-
<
|
|
41
|
-
|
|
48
|
+
<div class="private-key-input mt-2 p-3 rounded border border-neutral-300 bg-neutral-50 text-neutral-800">
|
|
49
|
+
<label for="privateKeyText" class="sr-only">Private Key</label>
|
|
50
|
+
<textarea id="privateKeyText" name="privateKey" autocomplete="off" autocapitalize="off" spellcheck="false"
|
|
51
|
+
placeholder="Paste your private key here" rows="3" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"></textarea>
|
|
42
52
|
<div class="file-upload">
|
|
43
|
-
<input type="file" id="privateKeyFile"
|
|
44
|
-
<label for="privateKeyFile" class="
|
|
53
|
+
<input type="file" id="privateKeyFile" class="sr-only">
|
|
54
|
+
<label for="privateKeyFile" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-slate-600 text-white hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 disabled:opacity-50 disabled:pointer-events-none shadow-sm">
|
|
45
55
|
<i data-icon="upload" class="icon-fw"></i> Upload Key File
|
|
46
56
|
</label>
|
|
47
57
|
</div>
|
|
48
|
-
<
|
|
49
|
-
|
|
58
|
+
<label for="passphraseInput" class="sr-only">Key Passphrase</label>
|
|
59
|
+
<input type="password" id="passphraseInput" name="passphrase" autocomplete="off" autocapitalize="off" spellcheck="false" enterkeyhint="go"
|
|
60
|
+
placeholder="Key password (if encrypted)" class="optional block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
50
61
|
</div>
|
|
51
62
|
</div>
|
|
52
|
-
|
|
53
|
-
<div class="login-buttons">
|
|
54
|
-
<button type="submit" class="
|
|
55
|
-
<button type="button" id="loginSettingsBtn" class="pure-button" aria-label="Settings" title="Settings">
|
|
56
|
-
<i data-icon="settings"></i>
|
|
57
|
-
</button>
|
|
63
|
+
|
|
64
|
+
<div class="login-buttons mt-2">
|
|
65
|
+
<button type="submit" class="inline-flex w-full items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none">Connect</button>
|
|
58
66
|
</div>
|
|
59
67
|
</form>
|
|
60
68
|
</div>
|
|
61
69
|
</dialog>
|
|
62
|
-
<dialog id="errorDialog" class="modal"
|
|
63
|
-
<div class="modal-content error-modal">
|
|
64
|
-
<button autofocus class="close-button">×</button>
|
|
65
|
-
<h2>Error</h2>
|
|
70
|
+
<dialog id="errorDialog" class="modal">
|
|
71
|
+
<div class="modal-content relative error-modal bg-red-50 border border-red-400 rounded-md shadow-md p-5 w-80 sm:w-96">
|
|
72
|
+
<button type="button" autofocus class="close-button absolute top-2 right-2 text-neutral-400 hover:text-neutral-600 text-xl leading-none p-0 bg-transparent border-0">×</button>
|
|
73
|
+
<h2 class="text-red-700">Error</h2>
|
|
66
74
|
<p id="errorMessage"></p>
|
|
67
75
|
</div>
|
|
68
76
|
</dialog>
|
|
69
77
|
<dialog id="promptDialog" class="modal">
|
|
70
|
-
<div class="modal-content prompt-modal">
|
|
71
|
-
<button autofocus class="close-button">×</button>
|
|
72
|
-
<h2 id="promptMessage"
|
|
78
|
+
<div class="modal-content relative prompt-modal bg-white text-slate-800 border border-neutral-300 rounded-md shadow-md p-5 w-80 sm:w-96">
|
|
79
|
+
<button type="button" autofocus class="close-button absolute top-2 right-2 text-neutral-400 hover:text-neutral-600 text-xl leading-none p-0 bg-transparent border-0">×</button>
|
|
80
|
+
<h2 id="promptMessage">Authentication Required</h2>
|
|
73
81
|
<form>
|
|
74
|
-
<div id="promptInputContainer"></div>
|
|
75
|
-
<button type="submit" class="
|
|
82
|
+
<div id="promptInputContainer" class="mb-4 space-y-2"></div>
|
|
83
|
+
<button type="submit" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none">Submit</button>
|
|
76
84
|
</form>
|
|
77
85
|
</div>
|
|
78
86
|
</dialog>
|
|
79
87
|
<dialog id="terminalSettingsDialog" class="modal">
|
|
80
|
-
<div class="modal-content">
|
|
81
|
-
<h2>Terminal Settings</h2>
|
|
82
|
-
<form id="terminalSettingsForm" class="
|
|
83
|
-
<fieldset>
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
88
|
+
<div class="modal-content relative bg-white text-slate-800 border border-neutral-300 rounded-md shadow-md p-6 w-80 sm:w-[36rem]">
|
|
89
|
+
<h2 class="text-lg font-semibold text-slate-900 mb-2">Terminal Settings</h2>
|
|
90
|
+
<form id="terminalSettingsForm" class="space-y-2">
|
|
91
|
+
<fieldset class="grid grid-cols-1 sm:grid-cols-[auto,1fr] gap-x-4 gap-y-3 items-center">
|
|
92
|
+
<legend class="sr-only">Terminal Options</legend>
|
|
93
|
+
<label for="fontSize" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Font Size</label>
|
|
94
|
+
<input type="number" id="fontSize" name="fontSize" min="8" max="72" required autocapitalize="off" spellcheck="false" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
95
|
+
|
|
96
|
+
<label for="fontFamily" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Font Family</label>
|
|
97
|
+
<input type="text" id="fontFamily" name="fontFamily" required autocapitalize="off" spellcheck="false" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
98
|
+
|
|
99
|
+
<label for="cursorBlink" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Cursor Blink</label>
|
|
100
|
+
<select id="cursorBlink" name="cursorBlink" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
92
101
|
<option value="true">On</option>
|
|
93
102
|
<option value="false">Off</option>
|
|
94
103
|
</select>
|
|
95
|
-
|
|
96
|
-
<label for="scrollback">Scrollback</label>
|
|
97
|
-
<input type="number" id="scrollback" name="scrollback" min="1" max="200000" required>
|
|
98
|
-
|
|
99
|
-
<label for="tabStopWidth">Tab Stop Width</label>
|
|
100
|
-
<input type="number" id="tabStopWidth" name="tabStopWidth" min="1" max="100" required>
|
|
101
|
-
|
|
102
|
-
<label for="bellStyle">Bell Style</label>
|
|
103
|
-
<select id="bellStyle" name="bellStyle">
|
|
104
|
+
|
|
105
|
+
<label for="scrollback" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Scrollback</label>
|
|
106
|
+
<input type="number" id="scrollback" name="scrollback" min="1" max="200000" required autocapitalize="off" spellcheck="false" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
107
|
+
|
|
108
|
+
<label for="tabStopWidth" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Tab Stop Width</label>
|
|
109
|
+
<input type="number" id="tabStopWidth" name="tabStopWidth" min="1" max="100" required autocapitalize="off" spellcheck="false" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 placeholder-slate-400 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
110
|
+
|
|
111
|
+
<label for="bellStyle" class="text-sm font-medium text-slate-700 sm:text-right pr-3 whitespace-nowrap">Bell Style</label>
|
|
112
|
+
<select id="bellStyle" name="bellStyle" class="block w-full rounded-md border border-slate-300 bg-white text-slate-900 px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
104
113
|
<option value="sound">Sound</option>
|
|
105
114
|
<option value="none">None</option>
|
|
106
115
|
</select>
|
|
107
|
-
|
|
108
|
-
<button type="submit" class="pure-button pure-button-primary">Save</button>
|
|
109
|
-
<button type="button" id="closeterminalSettingsBtn" class="pure-button">Cancel</button>
|
|
110
116
|
</fieldset>
|
|
117
|
+
<div class="flex gap-2 pt-4 justify-end">
|
|
118
|
+
<button type="submit" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-blue-600 text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:pointer-events-none">Save</button>
|
|
119
|
+
<button type="button" id="closeterminalSettingsBtn" class="inline-flex items-center justify-center rounded-md border border-transparent px-3 py-2 text-sm font-medium bg-slate-700 text-white hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 disabled:opacity-50 disabled:pointer-events-none">Cancel</button>
|
|
120
|
+
</div>
|
|
111
121
|
</form>
|
|
112
122
|
</div>
|
|
113
123
|
</dialog>
|
|
114
|
-
<div id="backdrop" class="backdrop"></div>
|
|
115
|
-
<button id="reconnectButton">Reconnect</button>
|
|
124
|
+
<div id="backdrop" class="backdrop hidden"></div>
|
|
125
|
+
<button type="button" id="reconnectButton" class="hidden fixed left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-[1001] bg-blue-600 hover:bg-blue-700 text-white text-sm px-5 py-2 rounded shadow focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">Reconnect</button>
|
|
116
126
|
<div class="box">
|
|
117
|
-
<div id="header"></div>
|
|
118
|
-
<div id="terminalContainer" class="terminal"></div>
|
|
119
|
-
<div id="bottomdiv">
|
|
120
|
-
<div
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
<button id="
|
|
126
|
-
<button id="
|
|
127
|
-
<button id="
|
|
128
|
-
<button id="
|
|
129
|
-
<button id="
|
|
127
|
+
<div id="header" class="hidden w-full text-center z-[99] h-6 leading-6 bg-green-600 text-white border-b border-neutral-200 shrink-0"></div>
|
|
128
|
+
<div id="terminalContainer" class="terminal hidden flex-1 min-h-0 w-[calc(100%-1px)] max-w-[100vw] mx-auto p-[2px] pb-[env(safe-area-inset-bottom)] overscroll-contain touch-pan-y overflow-hidden"></div>
|
|
129
|
+
<div id="bottomdiv" class="z-[99] h-6 flex items-center bg-neutral-800 text-neutral-100 border-t border-neutral-200 shrink-0">
|
|
130
|
+
<div id="menu" class="relative group px-2">
|
|
131
|
+
<button id="menuToggle" type="button" aria-controls="dropupContent" aria-expanded="false" class="inline-flex items-center gap-1 select-none">
|
|
132
|
+
<i data-icon="menu" class="w-5 h-5 inline-block"></i> Menu
|
|
133
|
+
</button>
|
|
134
|
+
<div id="dropupContent" class="hidden group-hover:block absolute bottom-full left-0 min-w-56 bg-neutral-50 text-neutral-700 text-base shadow-md border border-neutral-200 z-[101]">
|
|
135
|
+
<button type="button" id="clearLogBtn" class="hidden w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="trash-can" class="w-5 h-5 inline-block"></i> Clear Log</button>
|
|
136
|
+
<button type="button" id="stopLogBtn" class="hidden w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="settings" class="animate-spin origin-center w-5 h-5 inline-block"></i> Stop Log</button>
|
|
137
|
+
<button type="button" id="startLogBtn" class="w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="clipboard" class="w-5 h-5 inline-block"></i> Start Log</button>
|
|
138
|
+
<button type="button" id="downloadLogBtn" class="hidden w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="download" class="w-5 h-5 inline-block"></i> Download Log</button>
|
|
139
|
+
<button type="button" id="replayCredentialsBtn" class="w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="key" class="w-5 h-5 inline-block"></i> Credentials</button>
|
|
140
|
+
<button type="button" id="reauthBtn" class="w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="key" class="w-5 h-5 inline-block"></i> Switch User</button>
|
|
141
|
+
<button type="button" id="terminalSettingsBtn" class="w-full text-left px-4 py-3 hover:bg-neutral-200 whitespace-nowrap inline-flex items-center gap-3"><i data-icon="settings" class="w-5 h-5 inline-block"></i> Settings</button>
|
|
130
142
|
</div>
|
|
131
143
|
</div>
|
|
132
|
-
<div id="footer"></div>
|
|
133
|
-
<div id="status"></div>
|
|
144
|
+
<div id="footer" class="inline-block text-left px-[10px] border-l border-neutral-200"></div>
|
|
145
|
+
<div id="status" role="status" aria-live="polite" aria-atomic="true" class="inline-block text-left px-[10px] z-[100] border-x border-neutral-200"></div>
|
|
134
146
|
</div>
|
|
135
147
|
</div>
|
|
136
148
|
</body>
|