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

Add jupyverse dashboard #161

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 jupyverse/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .dashboard import Dashboard


def app():
Dashboard.run(title="Jupyverse Dashboard", log="jupyverse.log")
129 changes: 129 additions & 0 deletions jupyverse/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import asyncio
import atexit
import json
import sys
import subprocess
from typing import List, Optional

from rich.text import Text # type: ignore
from rich.table import Table # type: ignore
from textual import events # type: ignore
from textual.app import App # type: ignore
from textual.reactive import Reactive # type: ignore
from textual.widgets import Footer, ScrollView # type: ignore
from textual.widget import Widget # type: ignore

FPS: Optional[subprocess.Popen] = None


def stop_fps():
if FPS is not None:
FPS.terminate()


atexit.register(stop_fps)


class Dashboard(App):
"""A dashboard for Jupyverse"""

async def on_load(self, event: events.Load) -> None:
await self.bind("e", "show_endpoints", "Show endpoints")
await self.bind("l", "show_log", "Show log")
await self.bind("q", "quit", "Quit")

show = Reactive("endpoints")
body_change = asyncio.Event()
text = Text()
table = Table(title="API Summary")

def action_show_log(self) -> None:
self.show = "log"

def action_show_endpoints(self) -> None:
self.show = "endpoints"

def watch_show(self, show: str) -> None:
self.body_change.set()

async def on_mount(self, event: events.Mount) -> None:

footer = Footer()
body = ScrollView(auto_width=True)

await self.view.dock(footer, edge="bottom")
await self.view.dock(body)

async def add_content():
global FPS
cmd = ["fps-uvicorn", "--fps.show_endpoints"] + sys.argv[1:]
FPS = await asyncio.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
)
queues = [asyncio.Queue() for i in range(2)]
asyncio.create_task(get_log(queues))
asyncio.create_task(self._show_log(queues[0]))
asyncio.create_task(self._show_endpoints(queues[1]))
asyncio.create_task(self._change_body(body))

await self.call_later(add_content)

async def _change_body(self, body: Widget):
while True:
await self.body_change.wait()
self.body_change.clear()
if self.show == "endpoints":
await body.update(self.table)
elif self.show == "log":
await body.update(self.text)

async def _show_endpoints(self, queue: asyncio.Queue):
endpoint_marker = "ENDPOINT:"
get_endpoint = False
endpoints = []
while True:
line = await queue.get()
if endpoint_marker in line:
get_endpoint = True
elif get_endpoint:
break
if get_endpoint:
i = line.find(endpoint_marker) + len(endpoint_marker)
line = line[i:].strip()
if not line:
break
endpoint = json.loads(line)
endpoints.append(endpoint)

self.table.add_column("Path", justify="left", style="cyan", no_wrap=True)
self.table.add_column("Methods", justify="right", style="green")
self.table.add_column("Plugin", style="magenta")

for endpoint in endpoints:
path = endpoint["path"]
methods = ", ".join(endpoint["methods"])
plugin = ", ".join(endpoint["plugin"])
if "WEBSOCKET" in methods:
path = f"[cyan on red]{path}[/]"
self.table.add_row(path, methods, plugin)

self.body_change.set()

async def _show_log(self, queue: asyncio.Queue):
while True:
line = await queue.get()
self.text.append(line)
self.body_change.set()


async def get_log(queues: List[asyncio.Queue]):
assert FPS is not None
assert FPS.stderr is not None
while True:
line = await FPS.stderr.readline()
if line:
line = line.decode()
for queue in queues:
await queue.put(line)
else:
break
3 changes: 3 additions & 0 deletions plugins/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-auth

An [FPS](https://github.com/jupyter-server/fps) plugin implementing authentication.
3 changes: 3 additions & 0 deletions plugins/auth/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_auth
version = attr: fps_auth.__version__
description = An FPS plugin implementing authentication
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/contents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-contents

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the contents API.
3 changes: 3 additions & 0 deletions plugins/contents/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_contents
version = attr: fps_contents.__version__
description = An FPS plugin implementing the contents API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/jupyterlab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-jupyterlab

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the JupyterLab API.
3 changes: 3 additions & 0 deletions plugins/jupyterlab/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_jupyterlab
version = attr: fps_jupyterlab.__version__
description = An FPS plugin implementing the JupyterLab API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/kernels/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-kernels

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the kernels API.
3 changes: 3 additions & 0 deletions plugins/kernels/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_kernels
version = attr: fps_kernels.__version__
description = An FPS plugin implementing the kernels API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-lab

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the JupyterLab/RetroLab API.
3 changes: 3 additions & 0 deletions plugins/lab/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_lab
version = attr: fps_lab.__version__
description = An FPS plugin implementing the JupyterLab/RetroLab API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/login/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-login

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the login API.
3 changes: 3 additions & 0 deletions plugins/login/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_login
version = attr: fps_login.__version__
description = An FPS plugin implementing the login API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/nbconvert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-nbconvert

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the nbconvert API.
3 changes: 3 additions & 0 deletions plugins/nbconvert/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_nbconvert
version = attr: fps_nbconvert.__version__
description = An FPS plugin implementing the nbconvert API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/retrolab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-login

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the RetroLab API.
3 changes: 3 additions & 0 deletions plugins/retrolab/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_retrolab
version = attr: fps_retrolab.__version__
description = An FPS plugin implementing the RetroLab API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/terminals/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-terminals

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the terminals API.
3 changes: 3 additions & 0 deletions plugins/terminals/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_terminals
version = attr: fps_terminals.__version__
description = An FPS plugin implementing the terminals API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
3 changes: 3 additions & 0 deletions plugins/yjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# fps-yjs

An [FPS](https://github.com/jupyter-server/fps) plugin implementing the Yjs API.
3 changes: 3 additions & 0 deletions plugins/yjs/setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[metadata]
name = fps_yjs
version = attr: fps_yjs.__version__
description = An FPS plugin implementing the Yjs API
long_description = file: README.md
long_description_content_type = text/markdown

[options]
include_package_data = True
Expand Down
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ install_requires =
fps-terminals
fps-nbconvert
fps-yjs
rich
textual

[options.extras_require]
jupyterlab =
Expand All @@ -47,7 +49,7 @@ test =

[options.entry_points]
console_scripts =
jupyverse = fps_uvicorn.cli:app
jupyverse = jupyverse.cli:app

[flake8]
max-line-length = 100