aws-ssh-utils 0.3.2__tar.gz → 0.3.3__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.
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/PKG-INFO +1 -1
- aws_ssh_utils-0.3.3/aws_ssh_utils/__init__.py +1 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/app.py +3 -5
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/terminal.py +60 -5
- aws_ssh_utils-0.3.2/aws_ssh_utils/__init__.py +0 -1
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/.gitignore +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/LICENSE +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/README.md +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/cli_utils.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/__init__.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/ec2.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/emr.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/emr_all.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/commands/healthcheck.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/connection.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/ec2_utils.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/emr_utils.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/logging_utils.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/py.typed +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/session.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/aws_ssh_utils/ssh.py +0 -0
- {aws_ssh_utils-0.3.2 → aws_ssh_utils-0.3.3}/pyproject.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.3"
|
|
@@ -25,8 +25,8 @@ from aws_ssh_utils.cli_utils import detect_environment, detect_scrollback, rende
|
|
|
25
25
|
from aws_ssh_utils.connection import Attempt, Environment, Target, connect, open_shell_channel
|
|
26
26
|
from aws_ssh_utils.ec2_utils import ec2_name, ec2_target, get_running_ec2_instances
|
|
27
27
|
from aws_ssh_utils.emr_utils import emr_target, get_emr_clusters, get_emr_instances, group_role, group_sort_key
|
|
28
|
-
from aws_ssh_utils.logging_utils import
|
|
29
|
-
from aws_ssh_utils.terminal import ShellStatus, Terminal
|
|
28
|
+
from aws_ssh_utils.logging_utils import terminal_logging
|
|
29
|
+
from aws_ssh_utils.terminal import ShellStatus, Terminal, TerminalApp
|
|
30
30
|
|
|
31
31
|
if TYPE_CHECKING:
|
|
32
32
|
from mypy_boto3_ec2 import EC2Client
|
|
@@ -165,7 +165,7 @@ class ShellPane(TabPane):
|
|
|
165
165
|
self.query_ancestor(TabbedContent).remove_pane(self.id or '')
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
class SSHApp(
|
|
168
|
+
class SSHApp(TerminalApp):
|
|
169
169
|
"""The textual app"""
|
|
170
170
|
|
|
171
171
|
TITLE = "AWS SSH"
|
|
@@ -183,8 +183,6 @@ class SSHApp(LoggedApp):
|
|
|
183
183
|
}
|
|
184
184
|
.error { color: $error; }
|
|
185
185
|
"""
|
|
186
|
-
# Its priority ctrl+p binding would steal shell history navigation from terminals.
|
|
187
|
-
ENABLE_COMMAND_PALETTE = False
|
|
188
186
|
BINDINGS: ClassVar[list[BindingType]] = [
|
|
189
187
|
Binding('ctrl+q', 'quit', 'Quit', priority=True),
|
|
190
188
|
Binding('f12', 'toggle_sidebar', 'Toggle sidebar', priority=True),
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Textual widgets rendering SSH and SSM shell channels through the pyte VT100 emulator."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import datetime as dt
|
|
4
5
|
import time
|
|
5
6
|
from collections import deque
|
|
6
7
|
from collections.abc import Sequence
|
|
7
8
|
from functools import lru_cache
|
|
9
|
+
from importlib.metadata import version
|
|
8
10
|
from typing import Any
|
|
9
11
|
|
|
10
12
|
import pyte
|
|
@@ -228,17 +230,45 @@ class Terminal(Widget, can_focus=True):
|
|
|
228
230
|
event.prevent_default()
|
|
229
231
|
self.send(data)
|
|
230
232
|
|
|
231
|
-
def on_paste(self, event: events.Paste):
|
|
233
|
+
async def on_paste(self, event: events.Paste):
|
|
234
|
+
event.stop()
|
|
232
235
|
text = event.text
|
|
236
|
+
started = time.monotonic()
|
|
237
|
+
logger.bind(file_only=True).info(
|
|
238
|
+
'Paste shell received: chars={}; bracketed={}',
|
|
239
|
+
len(text),
|
|
240
|
+
BRACKETED_PASTE in self.vt.mode,
|
|
241
|
+
)
|
|
233
242
|
if BRACKETED_PASTE in self.vt.mode:
|
|
234
243
|
text = f'\x1b[200~{text}\x1b[201~'
|
|
235
|
-
self.
|
|
244
|
+
if self.scrolled:
|
|
245
|
+
self.scroll_history(-self.scrolled)
|
|
246
|
+
try:
|
|
247
|
+
# SSH flow control may block a large paste. Keep the app loop free
|
|
248
|
+
# so the reader can deliver output while input is still being sent.
|
|
249
|
+
await asyncio.to_thread(self._send_all, text.encode())
|
|
250
|
+
logger.bind(file_only=True).info(
|
|
251
|
+
'Paste shell sent: bytes={}; elapsed={:.3f}s',
|
|
252
|
+
len(text.encode()),
|
|
253
|
+
time.monotonic() - started,
|
|
254
|
+
)
|
|
255
|
+
except OSError:
|
|
256
|
+
logger.exception('Terminal paste failed')
|
|
257
|
+
self.post_message(self.Closed())
|
|
236
258
|
|
|
237
259
|
def send(self, text: str):
|
|
238
260
|
"""Send user input, jumping back to the live screen like other terminals do."""
|
|
239
261
|
if self.scrolled:
|
|
240
262
|
self.scroll_history(-self.scrolled)
|
|
241
|
-
self.
|
|
263
|
+
self._send_all(text.encode())
|
|
264
|
+
|
|
265
|
+
def _send_all(self, data: bytes):
|
|
266
|
+
"""A channel write may accept only one SSH packet or the available window."""
|
|
267
|
+
while data:
|
|
268
|
+
sent = self.channel.send(data)
|
|
269
|
+
if sent <= 0:
|
|
270
|
+
raise OSError('Terminal channel closed while sending input')
|
|
271
|
+
data = data[sent:]
|
|
242
272
|
|
|
243
273
|
@override
|
|
244
274
|
def render_line(self, y: int) -> Strip:
|
|
@@ -301,12 +331,37 @@ class ShellStatus(Horizontal):
|
|
|
301
331
|
self.query_one('.duration', Static).update(f"{hours:02}:{minutes:02}:{seconds:02}")
|
|
302
332
|
|
|
303
333
|
|
|
304
|
-
class
|
|
305
|
-
"""
|
|
334
|
+
class TerminalApp(LoggedApp):
|
|
335
|
+
"""Shared input handling for apps containing interactive shell terminals."""
|
|
306
336
|
|
|
307
337
|
# Its priority ctrl+p binding would steal shell history navigation from the terminal.
|
|
308
338
|
ENABLE_COMMAND_PALETTE = False
|
|
309
339
|
|
|
340
|
+
def on_mount(self):
|
|
341
|
+
logger.bind(file_only=True).info('Terminal input diagnostics v1; textual={}', version('textual'))
|
|
342
|
+
|
|
343
|
+
@override
|
|
344
|
+
async def on_event(self, event: events.Event) -> None:
|
|
345
|
+
if isinstance(event, (events.AppFocus, events.AppBlur)):
|
|
346
|
+
logger.bind(file_only=True).info('Terminal focus event: {}', type(event).__name__)
|
|
347
|
+
if isinstance(event, events.Paste) and not event.is_forwarded and not self.app_focus:
|
|
348
|
+
# A terminal's paste confirmation dialog can send the paste before
|
|
349
|
+
# FocusIn. Textual restores the previous widget for keys/mouse, but
|
|
350
|
+
# not pastes, so they otherwise go to an unfocused screen and vanish.
|
|
351
|
+
self.app_focus = True
|
|
352
|
+
if isinstance(event, events.Paste) and not event.is_forwarded:
|
|
353
|
+
logger.bind(file_only=True).info(
|
|
354
|
+
'Paste app received: chars={}; app_focus={}; target={}',
|
|
355
|
+
len(event.text),
|
|
356
|
+
self.app_focus,
|
|
357
|
+
type(self.focused).__name__,
|
|
358
|
+
)
|
|
359
|
+
await super().on_event(event)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class ShellApp(TerminalApp):
|
|
363
|
+
"""A single full-screen shell, exits when the shell closes."""
|
|
364
|
+
|
|
310
365
|
def __init__(self, via: str, client: ShellClient, channel: ShellChannel, scrollback: int = 0):
|
|
311
366
|
super().__init__()
|
|
312
367
|
self.via = via
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.2"
|
|
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
|