|
39 | 39 | POSIX_INPUT_BUFFER: List[str] = [] |
40 | 40 | CURRENT_INPUT_DISPLAY = "" |
41 | 41 |
|
| 42 | +LIVE_REFRESH_INTERVAL = 2.0 # seconds |
| 43 | +INPUT_POLL_INTERVAL = 0.05 # seconds |
| 44 | +COMMAND_INPUT_VISIBLE = False |
| 45 | + |
42 | 46 |
|
43 | 47 | def _set_input_display(text: str) -> None: |
44 | | - global CURRENT_INPUT_DISPLAY |
| 48 | + global CURRENT_INPUT_DISPLAY, COMMAND_INPUT_VISIBLE |
45 | 49 | CURRENT_INPUT_DISPLAY = text |
| 50 | + COMMAND_INPUT_VISIBLE = bool(text) and text.startswith(":") |
46 | 51 |
|
47 | 52 |
|
48 | 53 | def _clear_input_display() -> None: |
@@ -168,6 +173,10 @@ def _read_nonblocking_command() -> Optional[str]: |
168 | 173 | _clear_input_display() |
169 | 174 | # Enter 입력 시 CR/LF 잔여 문자를 비움 |
170 | 175 | continue |
| 176 | + if char == "\x1b": |
| 177 | + WINDOWS_INPUT_BUFFER.clear() |
| 178 | + _clear_input_display() |
| 179 | + continue |
171 | 180 | if char in ("\b", "\x7f"): |
172 | 181 | if WINDOWS_INPUT_BUFFER: |
173 | 182 | WINDOWS_INPUT_BUFFER.pop() |
@@ -205,6 +214,20 @@ def _read_nonblocking_command() -> Optional[str]: |
205 | 214 | POSIX_INPUT_BUFFER.clear() |
206 | 215 | _clear_input_display() |
207 | 216 | break |
| 217 | + if char == "\x1b": |
| 218 | + POSIX_INPUT_BUFFER.clear() |
| 219 | + _clear_input_display() |
| 220 | + while True: |
| 221 | + readable, _, _ = select.select([fd], [], [], 0) |
| 222 | + if not readable: |
| 223 | + break |
| 224 | + try: |
| 225 | + leftover = posix_os.read(fd, 1) |
| 226 | + except OSError: |
| 227 | + break |
| 228 | + if not leftover: |
| 229 | + break |
| 230 | + continue |
208 | 231 | if char in ("\x7f", "\b"): |
209 | 232 | if POSIX_INPUT_BUFFER: |
210 | 233 | POSIX_INPUT_BUFFER.pop() |
@@ -285,13 +308,24 @@ def update( |
285 | 308 | self.last_frame = None |
286 | 309 | if frame_key != self.last_frame: |
287 | 310 | self.live.update(renderable) |
| 311 | + self.live.refresh() |
288 | 312 | self.last_frame = frame_key |
289 | 313 | if snapshot_markdown is not None: |
290 | 314 | self.latest_snapshot = snapshot_markdown |
291 | 315 |
|
292 | | - def tick(self) -> None: |
293 | | - _tick_iteration(self.live, self.latest_snapshot) |
294 | | - time.sleep(2) |
| 316 | + def tick(self, interval: float = LIVE_REFRESH_INTERVAL) -> None: |
| 317 | + deadline = time.monotonic() + interval |
| 318 | + while True: |
| 319 | + previous_input = self.last_input_state |
| 320 | + _tick_iteration(self.live, self.latest_snapshot) |
| 321 | + current_input = CURRENT_INPUT_DISPLAY |
| 322 | + if current_input != previous_input: |
| 323 | + self.last_input_state = current_input |
| 324 | + self.live.refresh() |
| 325 | + remaining = deadline - time.monotonic() |
| 326 | + if remaining <= 0: |
| 327 | + break |
| 328 | + time.sleep(min(INPUT_POLL_INTERVAL, remaining)) |
295 | 329 |
|
296 | 330 |
|
297 | 331 | def _parse_cpu_to_millicores(value: str) -> int: |
@@ -365,33 +399,26 @@ def _command_panel(command: str) -> Panel: |
365 | 399 | ) |
366 | 400 |
|
367 | 401 |
|
368 | | -class _CommandInputRenderable: |
369 | | - """현재 입력 버퍼 상태를 실시간으로 렌더링.""" |
| 402 | +class _CommandInputPanel: |
| 403 | + """COMMAND_INPUT_VISIBLE 상태에 따라 패널을 조건부로 출력.""" |
370 | 404 |
|
371 | 405 | def __rich_console__(self, console: Console, options): # type: ignore[override] |
| 406 | + if not COMMAND_INPUT_VISIBLE: |
| 407 | + return |
372 | 408 | display = CURRENT_INPUT_DISPLAY |
373 | | - if display: |
374 | | - prompt = display if display.startswith(":") else f":{display}" |
375 | | - style = "bold cyan" |
376 | | - else: |
377 | | - prompt = ":" |
378 | | - style = "dim" |
379 | | - yield Text(prompt, style=style) |
380 | | - |
381 | | - |
382 | | -def _input_prompt_panel() -> Panel: |
383 | | - """현재 명령 입력 상태를 표시하는 패널.""" |
384 | | - return Panel( |
385 | | - _CommandInputRenderable(), |
386 | | - title="command input", |
387 | | - border_style="cyan", |
388 | | - ) |
| 409 | + prompt = display if display else ":" |
| 410 | + style = "bold cyan" if display else "dim" |
| 411 | + yield Panel( |
| 412 | + Text(prompt, style=style), |
| 413 | + title="command input", |
| 414 | + border_style="cyan", |
| 415 | + ) |
389 | 416 |
|
390 | 417 |
|
391 | 418 | def _compose_group(command: str, *renderables: RenderableType) -> Group: |
392 | 419 | """메인 콘텐츠와 kubectl 명령을 하단에 배치한 그룹 구성.""" |
393 | 420 | items: List[RenderableType] = [] |
394 | | - items.append(_input_prompt_panel()) |
| 421 | + items.append(_CommandInputPanel()) |
395 | 422 | items.extend(renderables) |
396 | 423 | items.append(_command_panel(command)) |
397 | 424 | return Group(*items) |
@@ -738,7 +765,7 @@ def watch_event_monitoring() -> None: |
738 | 765 |
|
739 | 766 | try: |
740 | 767 | with suppress_terminal_echo(): |
741 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 768 | + with Live(console=console, auto_refresh=False) as live: |
742 | 769 | tracker = LiveFrameTracker(live) |
743 | 770 | while True: |
744 | 771 | stdout, error = _run_shell_command(full_cmd) |
@@ -920,7 +947,7 @@ def watch_pod_monitoring_by_creation() -> None: |
920 | 947 |
|
921 | 948 | try: |
922 | 949 | with suppress_terminal_echo(): |
923 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 950 | + with Live(console=console, auto_refresh=False) as live: |
924 | 951 | tracker = LiveFrameTracker(live) |
925 | 952 | while True: |
926 | 953 | stdout, error = _run_shell_command(full_cmd) |
@@ -1027,7 +1054,7 @@ def watch_non_running_pod() -> None: |
1027 | 1054 |
|
1028 | 1055 | try: |
1029 | 1056 | with suppress_terminal_echo(): |
1030 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1057 | + with Live(console=console, auto_refresh=False) as live: |
1031 | 1058 | tracker = LiveFrameTracker(live) |
1032 | 1059 | while True: |
1033 | 1060 | stdout, error = _run_shell_command(full_cmd) |
@@ -1125,7 +1152,7 @@ def watch_pod_counts() -> None: |
1125 | 1152 | v1 = client.CoreV1Api() |
1126 | 1153 | try: |
1127 | 1154 | with suppress_terminal_echo(): |
1128 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1155 | + with Live(console=console, auto_refresh=False) as live: |
1129 | 1156 | tracker = LiveFrameTracker(live) |
1130 | 1157 | while True: |
1131 | 1158 | pods = get_pods(v1, ns) |
@@ -1221,7 +1248,7 @@ def watch_node_monitoring_by_creation() -> None: |
1221 | 1248 |
|
1222 | 1249 | try: |
1223 | 1250 | with suppress_terminal_echo(): |
1224 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1251 | + with Live(console=console, auto_refresh=False) as live: |
1225 | 1252 | tracker = LiveFrameTracker(live) |
1226 | 1253 | while True: |
1227 | 1254 | stdout, error = _run_shell_command(full_cmd) |
@@ -1341,7 +1368,7 @@ def watch_unhealthy_nodes() -> None: |
1341 | 1368 |
|
1342 | 1369 | try: |
1343 | 1370 | with suppress_terminal_echo(): |
1344 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1371 | + with Live(console=console, auto_refresh=False) as live: |
1345 | 1372 | tracker = LiveFrameTracker(live) |
1346 | 1373 | while True: |
1347 | 1374 | stdout, error = _run_shell_command(full_cmd) |
@@ -1472,7 +1499,7 @@ def watch_node_resources() -> None: |
1472 | 1499 |
|
1473 | 1500 | try: |
1474 | 1501 | with suppress_terminal_echo(): |
1475 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1502 | + with Live(console=console, auto_refresh=False) as live: |
1476 | 1503 | tracker = LiveFrameTracker(live) |
1477 | 1504 | while True: |
1478 | 1505 | stdout, error = _run_shell_command(full_cmd) |
@@ -1605,7 +1632,7 @@ def watch_pod_resources() -> None: |
1605 | 1632 |
|
1606 | 1633 | try: |
1607 | 1634 | with suppress_terminal_echo(): |
1608 | | - with Live(console=console, refresh_per_second=4.0) as live: |
| 1635 | + with Live(console=console, auto_refresh=False) as live: |
1609 | 1636 | tracker = LiveFrameTracker(live) |
1610 | 1637 | while True: |
1611 | 1638 | metrics, error, kubectl_cmd = _get_kubectl_top_pod(namespace) |
|
0 commit comments