From 8dc75da1e77b9909258057f637003576cbbae53c Mon Sep 17 00:00:00 2001 From: jialei Date: Tue, 12 Jul 2022 13:02:05 +0800 Subject: [PATCH] feat(client): board support show project tree and model list --- client/Makefile | 4 + client/starwhale/cli/__init__.py | 2 +- client/starwhale/cli/board/__init__.py | 0 client/starwhale/cli/{ => board}/board.py | 94 +++++----------------- client/starwhale/cli/board/project_tree.py | 56 +++++++++++++ 5 files changed, 83 insertions(+), 73 deletions(-) create mode 100644 client/starwhale/cli/board/__init__.py rename client/starwhale/cli/{ => board}/board.py (65%) create mode 100644 client/starwhale/cli/board/project_tree.py diff --git a/client/Makefile b/client/Makefile index b007693395..12d8036412 100644 --- a/client/Makefile +++ b/client/Makefile @@ -32,6 +32,10 @@ black-format: isort-format: isort $(PY_CHANGED_FILES) +.POHNY: format +format: + black --config pyproject.toml . && isort . + .POHNY: ci-format-checker ci-format-checker: echo "run black" diff --git a/client/starwhale/cli/__init__.py b/client/starwhale/cli/__init__.py index cadc662646..84e7668e3e 100644 --- a/client/starwhale/cli/__init__.py +++ b/client/starwhale/cli/__init__.py @@ -8,13 +8,13 @@ from starwhale.core.job.cli import job_cmd from starwhale.utils.config import load_swcli_config from starwhale.core.model.cli import model_cmd +from starwhale.cli.board.board import open_board from starwhale.core.dataset.cli import dataset_cmd from starwhale.core.project.cli import project_cmd from starwhale.core.runtime.cli import runtime_cmd from starwhale.core.instance.cli import instance_cmd from .mngt import add_mngt_command -from .board import open_board def create_sw_cli() -> click.core.Group: diff --git a/client/starwhale/cli/board/__init__.py b/client/starwhale/cli/board/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/starwhale/cli/board.py b/client/starwhale/cli/board/board.py similarity index 65% rename from client/starwhale/cli/board.py rename to client/starwhale/cli/board/board.py index 898705553f..da5ae1f751 100644 --- a/client/starwhale/cli/board.py +++ b/client/starwhale/cli/board/board.py @@ -11,12 +11,10 @@ from textual.widget import Widget, RenderableType from textual.widgets import Footer, Header, ScrollView -from ..utils import pretty_bytes, snake_to_camel -from ..consts import STANDALONE_INSTANCE -from ..core.model.view import ModelTermView -from ..core.instance.view import InstanceTermView -from ..core.project.model import Project -from ..core.instance.model import Instance, CloudInstance, StandaloneInstance +from starwhale.utils import pretty_bytes, snake_to_camel +from starwhale.core.model.view import ModelTermView + +from .project_tree import ProjectTree, ProjectClick @dataclass @@ -58,6 +56,7 @@ def watch_data(self): pass def render(self) -> Table: + self.app.sub_title = self.__class__.__name__ return self._info or self.table def reload(self) -> None: @@ -122,7 +121,8 @@ async def key_h(self): class Models(TableWidget): """Models represents starwhale model view""" - def __init__(self, **kwargs) -> None: + # TODO use constance + def __init__(self, uri: str = "local/self", **kwargs) -> None: super().__init__(**kwargs) self.render_fn = [ Column("name"), @@ -131,89 +131,39 @@ def __init__(self, **kwargs) -> None: Column("size", render=lambda _, x: pretty_bytes(x["size"])), Column("created_at", "Created At"), ] - self.reload() - - def reloadImpl(self) -> None: - self.data, _ = ModelTermView("local/self").list() - - -class Projects(TableWidget): - """Projects represents starwhale project view""" - - def __init__(self, uri: str, **kwargs): - super().__init__(**kwargs) - self.render_fn = [ - Column("name"), - Column("owner"), - Column("created_at", "Created At"), - ] self.uri = uri self.reload() - self.current = None - - def reloadImpl(self) -> None: - self.data, _ = Project.list(self.uri) - - -class Instances(TableWidget): - """Instance represents starwhale instance view""" - - def __init__(self, **kwargs) -> None: - super().__init__(**kwargs) - self.render_fn = [ - Column("name"), - Column("uri"), - Column("user_name"), - Column("user_role"), - Column("current_project"), - Column("updated_at", "Last Update"), - ] - self.reload() - - self.current = None - - show_projects: Reactive[bool] = Reactive(False) - def reloadImpl(self) -> None: - self.data = InstanceTermView().list() - - def get_current(self) -> Instance: - row = self.data[self.cursor_line] - # TODO refine instance detection logic - name = row["name"] - if name == STANDALONE_INSTANCE: - return StandaloneInstance() - return CloudInstance(name) - - async def key_enter(self): - self.show_projects = True - - def key_h(self): - self.show_projects = False - - def render(self) -> Table: - if self.show_projects: - return Projects(uri=self.get_current().uri.raw).render() - return super().render() + self.data, _ = ModelTermView.list(self.uri) class Dashboard(App): def __init__(self, **kwargs): - self.body: t.Union[ScrollView, None] = None - self.table = Instances() super().__init__(**kwargs) async def on_load(self, event: events.Load) -> None: pass async def on_mount(self, event: events.Mount) -> None: + self.body = ScrollView() await self.view.dock(Header(style=""), edge="top") await self.view.dock(Footer(), edge="bottom") - await self.view.dock(self.table, edge="right") + await self.view.dock( + ScrollView(ProjectTree("Starwhale", "projects")), + edge="left", + size=30, + name="sidebar", + ) + await self.view.dock(self.body, edge="top") async def on_key(self, event: events.Key) -> None: - await self.table.on_key(event) + pass + + async def handle_project_click(self, message: ProjectClick) -> None: + self.model = Models(message.uri) + await self.body.update(self.model) + await self.model.focus() @click.command( diff --git a/client/starwhale/cli/board/project_tree.py b/client/starwhale/cli/board/project_tree.py new file mode 100644 index 0000000000..c8e2a124f3 --- /dev/null +++ b/client/starwhale/cli/board/project_tree.py @@ -0,0 +1,56 @@ +from dataclasses import dataclass + +import rich.repr +from textual import events +from textual._types import MessageTarget +from textual.widget import Message +from textual.widgets import TreeClick, TreeControl + +from starwhale.core.instance.view import InstanceTermView +from starwhale.core.project.model import Project + + +@dataclass +class ProjectEntry: + path: str + is_project: bool + + +@rich.repr.auto +class ProjectClick(Message, bubble=True): + def __init__(self, sender: MessageTarget, uri: str) -> None: + self.uri = uri + super().__init__(sender) + + +class ProjectTree(TreeControl[ProjectEntry]): + def __init__(self, uri: str, name: str): + data = ProjectEntry("", False) + super().__init__(uri, name=name, data=data) + + async def on_mount(self, event: events.Mount) -> None: + ins = InstanceTermView().list() + for i in ins: + await self.root.add(i["name"], ProjectEntry(i["name"], False)) + await self.root.expand() + self.refresh(layout=True) + + async def handle_tree_click(self, message: TreeClick[ProjectEntry]) -> None: + dir_entry = message.node.data + # root entry + if not dir_entry.path: + return + if dir_entry.is_project: + await self.emit(ProjectClick(self, dir_entry.path)) + else: + if not message.node.loaded: + ins = message.node.data.path + ps, _ = Project.list(ins) + for i in ps: + await message.node.add( + i["name"], ProjectEntry(f"{ins}/{i['name']}", True) + ) + await message.node.expand() + message.node.loaded = True + else: + await message.node.toggle()