Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First rudimentary version of the web UI #371

Merged
merged 42 commits into from
Aug 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8b2fb88
Hello world for the initial webui
achimnol Aug 11, 2023
10c23ce
Adopt htmx and tailwindcss to build a simple web UI
achimnol Aug 11, 2023
84bf213
Remove mis-added file
achimnol Aug 11, 2023
7bcf675
Refactor a little bit
achimnol Aug 11, 2023
23edf87
Implement the live task list (simple 2sec interval polling)
achimnol Aug 11, 2023
ceaa6ac
Adopt mustache.js for client-side template rendering
achimnol Aug 19, 2023
938c596
Introduce trafaret for query param validation & conversion
achimnol Aug 19, 2023
8ec6688
Implement task cancellation button with response notifications
achimnol Aug 19, 2023
4ab8bdf
Add animation to the notification
achimnol Aug 19, 2023
4baaee3
Prevent accidental cancellation of the entire event loop
achimnol Aug 19, 2023
49d8fa8
Implement the terminated task list with a tab ui
achimnol Aug 20, 2023
315ec64
Implement the persistent filter option
achimnol Aug 21, 2023
3d1f549
Include the official "forms" plugin of TailwindCSS
achimnol Aug 21, 2023
78fcf9f
Implement live search by persistent and coro/name keyword filter
achimnol Aug 21, 2023
6315a1d
Refine the task counters and toolbar UI
achimnol Aug 21, 2023
4981792
Remove debug print
achimnol Aug 21, 2023
e0b02c5
Fix task cancellation button
achimnol Aug 21, 2023
a0f563d
Make it more standard-compliant
achimnol Aug 21, 2023
9b0876f
Found a workaround for bigskysoftware/htmx#1119
achimnol Aug 21, 2023
b06c896
Make the htmx attr style consistent
achimnol Aug 21, 2023
2561edb
Display the webui hyperlink in the log message when starting the monitor
achimnol Aug 21, 2023
8c57ac8
Refactor the webui's server-side API parameter validation
achimnol Aug 21, 2023
444b4db
Ensure Python 3.8+ compatibility regarding StrEnum
achimnol Aug 21, 2023
495d3f4
Refine notifications
achimnol Aug 21, 2023
4d0c363
Protocol shows the port type
achimnol Aug 21, 2023
0eb13e3
Add the trace pages (skeleton)
achimnol Aug 22, 2023
2646b84
Reimplement task creation/termination chain logs for the webui
achimnol Aug 22, 2023
ebf3dbd
Fix the cancel command in termui
achimnol Aug 22, 2023
34f84e4
Rename for consistency
achimnol Aug 22, 2023
d83ea75
Make stack formatting to use strict types
achimnol Aug 22, 2023
64cd5dc
Update the test code
achimnol Aug 22, 2023
816eb76
Update examples
achimnol Aug 22, 2023
6dbc04c
Use literal annotations in the test module
achimnol Aug 22, 2023
e7dc3db
Add news fragment
achimnol Aug 22, 2023
c08c0e8
Update test mock target for `print_formatted_text`
achimnol Aug 22, 2023
9449782
Fix Python 3.8/3.9 compat
achimnol Aug 22, 2023
219d6bf
Ensure correct event loops in test codes
achimnol Aug 26, 2023
d61f356
Fix potential deadlocks in tests and the console command
achimnol Aug 26, 2023
b9e5181
Some cleanup
achimnol Aug 26, 2023
54c507b
Ensure termination of console when there are assertion failures
achimnol Aug 26, 2023
6bf251f
Try to fix the docs build (maybe broken due to explicit jinja2 depend…
achimnol Aug 26, 2023
5189b48
doc: Adopt the latest Sphinx and update some configs
achimnol Aug 26, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ trim_trailing_whitespace = false
max_line_length = 0
indent_style = space
indent_size = 3

[*.html]
max_line_length = 0
indent_style = space
indent_size = 2
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ Pipfile.lock
.python-version

.DS_Store
._.DS_Store
.vim/
9 changes: 6 additions & 3 deletions aiomonitor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@

from importlib.metadata import version

from .monitor import MONITOR_TERMUI_PORT # for backward compatibility
from .monitor import (
CONSOLE_PORT,
MONITOR_HOST,
MONITOR_PORT,
MONITOR_WEBUI_PORT,
Monitor,
monitor_cli,
start_monitor,
)
from .termui.commands import monitor_cli

__all__ = (
"Monitor",
"monitor_cli",
"start_monitor",
"monitor_cli",
"MONITOR_HOST",
"MONITOR_PORT",
"MONITOR_TERMUI_PORT",
"MONITOR_WEBUI_PORT",
"CONSOLE_PORT",
)
__version__ = version("aiomonitor")
4 changes: 2 additions & 2 deletions aiomonitor/cli.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import argparse
import asyncio

from .monitor import MONITOR_HOST, MONITOR_PORT
from .monitor import MONITOR_HOST, MONITOR_TERMUI_PORT
from .telnet import TelnetClient


Expand Down Expand Up @@ -31,7 +31,7 @@ def main() -> None:
"-p",
"--port",
dest="monitor_port",
default=MONITOR_PORT,
default=MONITOR_TERMUI_PORT,
type=int,
help="monitor port number",
)
Expand Down
67 changes: 27 additions & 40 deletions aiomonitor/console.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@


class ConsoleProxy:
# Runs inside the monitored event loop

def __init__(
self,
stdin: Input,
Expand Down Expand Up @@ -49,7 +51,8 @@ async def interact(self) -> None:
await self._closed.wait()
finally:
self._closed.set()
self._conn_writer.write_eof()
if not self._conn_writer.is_closing():
self._conn_writer.write_eof()

async def _handle_user_input(self) -> None:
prompt_session: PromptSession[str] = PromptSession(
Expand Down Expand Up @@ -81,7 +84,7 @@ async def _handle_received(self) -> None:
return
self._stdout.write_raw(buf.decode("utf8"))
self._stdout.flush()
except asyncio.CancelledError:
except (ConnectionResetError, asyncio.CancelledError):
pass
finally:
self._closed.set()
Expand All @@ -93,65 +96,49 @@ async def start(
locals: Optional[Dict[str, Any]],
monitor_loop: asyncio.AbstractEventLoop,
) -> asyncio.AbstractServer:
ui_loop = asyncio.get_running_loop()
done = asyncio.Event()

async def _start(done: asyncio.Event) -> asyncio.AbstractServer:
async def _start() -> asyncio.AbstractServer:
# Runs inside the monitored event loop
def _factory(streams: Any = None) -> aioconsole.AsynchronousConsole:
return aioconsole.AsynchronousConsole(
locals=locals, streams=streams, loop=monitor_loop
)

try:
server = await aioconsole.start_interactive_server(
host=host,
port=port,
factory=_factory,
loop=monitor_loop,
)
finally:
ui_loop.call_soon_threadsafe(done.set)
server = await aioconsole.start_interactive_server(
host=host,
port=port,
factory=_factory,
loop=monitor_loop,
)
return server

console_future = asyncio.run_coroutine_threadsafe(
_start(done),
loop=monitor_loop,
console_future = asyncio.wrap_future(
asyncio.run_coroutine_threadsafe(
_start(),
loop=monitor_loop,
)
)
try:
await done.wait()
finally:
server = console_future.result()
return server
return await console_future


async def close(
server: asyncio.AbstractServer,
monitor_loop: asyncio.AbstractEventLoop,
) -> None:
ui_loop = asyncio.get_running_loop()
done = asyncio.Event()

async def _close(done: asyncio.Event) -> None:
async def _close() -> None:
# Runs inside the monitored event loop
try:
server.close()
await server.wait_closed()
except NotImplementedError:
pass
finally:
ui_loop.call_soon_threadsafe(done.set)

close_future = asyncio.run_coroutine_threadsafe(
_close(done),
loop=monitor_loop,
close_future = asyncio.wrap_future(
asyncio.run_coroutine_threadsafe(
_close(),
loop=monitor_loop,
)
)
try:
# NOTE: If the monitor is interrupted before the aioconsole session is closed,
# run_coroutine_threadsafe() seems to get deadlocked.
# In that case, you may need to send SIGINT multiple times to the
# aiomonitor-running process.
await done.wait()
finally:
close_future.result()
await close_future


async def proxy(sin: Input, sout: Output, host: str, port: int) -> None:
Expand Down
12 changes: 12 additions & 0 deletions aiomonitor/context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from __future__ import annotations

import asyncio
from contextvars import ContextVar
from typing import TYPE_CHECKING, TextIO

if TYPE_CHECKING:
from .monitor import Monitor

current_monitor: ContextVar[Monitor] = ContextVar("current_monitor")
current_stdout: ContextVar[TextIO] = ContextVar("current_stdout")
command_done: ContextVar[asyncio.Event] = ContextVar("command_done")
7 changes: 7 additions & 0 deletions aiomonitor/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import annotations


class MissingTask(Exception):
def __init__(self, task_id: str | int) -> None:
super().__init__(task_id)
self.task_id = task_id
Loading
Loading