Example #1
0
class Listener:
    def __init__(self, app: 'App'):
        self._app = app
        self._running = False

        self._loop: Optional[asyncio.AbstractEventLoop] = None
        self._queue: Optional[asyncio.Queue] = None

        self._thread: Optional[Thread] = None
        self._prompt_app: Optional[Application] = None

    def start(self):
        self._running = True
        self._queue = asyncio.Queue(1)

        self._loop = asyncio.get_event_loop()
        self._loop.create_task(self._execute())

        kb = KeyBindings()
        kb.add(Keys.Escape)(self._handle)
        kb.add(Keys.ControlC)(self._exit)
        self._prompt_app = Application(layout=Layout(Window()),
                                       key_bindings=kb)

        self._thread = Thread(target=self._prompt_app.run)
        self._thread.start()

    def stop(self):
        self._running = False
        if self._prompt_app:
            self._prompt_app.exit()
            self._prompt_app = None

    def _handle(self, event: KeyPressEvent):
        async def _put_threadsafe():
            try:
                key = event.key_sequence[0].key
                self._queue.put_nowait(key)
            except asyncio.QueueFull:
                pass

        asyncio.run_coroutine_threadsafe(_put_threadsafe(), self._loop)

    def _exit(self, event: KeyPressEvent):
        self.stop()
        self._app.close()

    async def _execute(self):
        key = await self._queue.get()

        self.stop()
        try:
            handler = Handler()
            await handler.handle(key, self._app)
        finally:
            self.start()
Example #2
0
class Notebook(Format, KeyBindings):

    app: Optional[Application]
    layout: Layout
    copied_cell: Optional[Cell]
    console: Console
    _run_notebook_nb_path: str
    cells: List[Cell]
    executing_cells: List[Cell]
    json: Dict[str, Any]
    kd: Optional[KernelDriver]
    execution_count: int
    current_cell_idx: int
    idle: Optional[asyncio.Event]

    def __init__(self, nb_path: str, no_kernel: bool = False):
        self.app = None
        self.copied_cell = None
        self.console = Console()
        self.nb_path = nb_path
        self.executing_cells = []
        if os.path.exists(nb_path):
            self.read_nb()
        else:
            self.create_nb()
        kernel_name = self.json["metadata"]["kernelspec"]["name"]
        if no_kernel:
            self.kd = None
        else:
            try:
                self.kd = KernelDriver(kernel_name=kernel_name, log=False)
                kernel_driver.driver._output_hook_default = self.output_hook
            except RuntimeError:
                self.kd = None
        self.execution_count = 1
        self.current_cell_idx = 0
        self.idle = None

    @property
    def current_cell(self):
        return self.cells[self.current_cell_idx]

    def run(self, save_path: str = ""):
        asyncio.run(self._run())
        if not save_path:
            i = self.nb_path.rfind(".")
            self._run_notebook_path = self.nb_path[:i] + "_run" + self.nb_path[
                i:]
        else:
            self._run_notebook_path = save_path
        self.save(self._run_notebook_path)

    def show(self):
        self.key_bindings = PtKeyBindings()
        self.bind_keys()
        self.create_layout()
        self.edit_mode = False
        self.app = Application(layout=self.layout,
                               key_bindings=self.key_bindings,
                               full_screen=True)
        self.focus(0)
        asyncio.run(self._show())

    def update_layout(self, idx: int):
        if self.app:
            self.create_layout()
            self.app.layout = self.layout
        self.focus(idx)

    def create_layout(self):
        inout_cells = list(
            itertools.chain.from_iterable([(
                VSplit([cell.input_prefix, cell.input]),
                VSplit([cell.output_prefix, ONE_COL, cell.output]),
            ) for cell in self.cells]))
        root_container = ScrollablePane(HSplit(inout_cells))
        self.layout = Layout(root_container)

    def focus(self, idx: int):
        if 0 <= idx < len(self.cells):
            if self.app:
                self.app.layout.focus(self.cells[idx].input_window)
            self.current_cell_idx = idx

    def exit_cell(self):
        self.edit_mode = False
        self.current_cell.update_json()
        self.current_cell.set_input_readonly()

    def enter_cell(self):
        self.edit_mode = True
        self.current_cell.set_input_editable()

    def move_up(self):
        idx = self.current_cell_idx
        if idx > 0:
            self.cells[idx -
                       1], self.cells[idx] = self.cells[idx], self.cells[idx -
                                                                         1]
            self.update_layout(idx - 1)

    def move_down(self):
        idx = self.current_cell_idx
        if idx < len(self.cells) - 1:
            self.cells[idx], self.cells[idx +
                                        1] = self.cells[idx +
                                                        1], self.cells[idx]
            self.update_layout(idx + 1)

    def clear_output(self):
        self.current_cell.clear_output()

    def markdown_cell(self):
        self.current_cell.set_as_markdown()

    def code_cell(self):
        self.current_cell.set_as_code()

    async def run_cell(self, and_select_below: bool = False):
        if self.kd:
            self.executing_cells.append(self.current_cell)
            if and_select_below:
                if self.current_cell_idx == len(self.cells) - 1:
                    self.insert_cell(self.current_cell_idx + 1)
                self.focus(self.current_cell_idx + 1)
            await self.executing_cells[-1].run()

    def cut_cell(self):
        idx = self.current_cell_idx
        self.copied_cell = self.cells.pop(idx)
        if not self.cells:
            self.cells = [Cell(self)]
        elif idx == len(self.cells):
            idx -= 1
        self.update_layout(idx)

    def copy_cell(self):
        idx = self.current_cell_idx
        self.copied_cell = self.cells[idx]

    def paste_cell(self, below=False):
        idx = self.current_cell_idx + below
        if self.copied_cell is not None:
            pasted_cell = self.copied_cell.copy()
            self.cells.insert(idx, pasted_cell)
            self.update_layout(idx)

    def insert_cell(self, below=False):
        idx = self.current_cell_idx + below
        self.cells.insert(idx, Cell(self))
        self.update_layout(idx)

    def output_hook(self, msg: Dict[str, Any]):
        msg_type = msg["header"]["msg_type"]
        content = msg["content"]
        outputs = self.executing_cells[0].json["outputs"]
        if msg_type == "stream":
            if (not outputs) or (outputs[-1]["name"] != content["name"]):
                outputs.append({
                    "name": content["name"],
                    "output_type": msg_type,
                    "text": []
                })
            outputs[-1]["text"].append(content["text"])
        elif msg_type in ("display_data", "execute_result"):
            outputs.append({
                "data": {
                    "text/plain": [content["data"].get("text/plain", "")]
                },
                "execution_count": self.execution_count,
                "metadata": {},
                "output_type": msg_type,
            })
            text = rich_print(f"Out[{self.execution_count}]:",
                              self.console,
                              style="red",
                              end="")
            self.executing_cells[
                0].output_prefix.content = FormattedTextControl(
                    text=ANSI(text))
        elif msg_type == "error":
            outputs.append({
                "ename": content["ename"],
                "evalue": content["evalue"],
                "output_type": "error",
                "traceback": content["traceback"],
            })
        else:
            return
        text, height = get_output_text_and_height(outputs, self.console)
        self.executing_cells[0].output.content = FormattedTextControl(
            text=text)
        self.executing_cells[0].output.height = height
        if self.app:
            self.app.invalidate()

    @property
    def run_notebook_path(self):
        return self._run_notebook_path

    async def _run(self):
        await self.kd.start()
        while True:
            self.executing_cells = [self.current_cell]
            await self.current_cell.run()
            if self.current_cell_idx == len(self.cells) - 1:
                break
            self.focus(self.current_cell_idx + 1)

    async def _show(self):
        if self.kd:
            asyncio.create_task(self.kd.start())
        await self.app.run_async()

    async def exit(self):
        if self.kd:
            await self.kd.stop()
        self.app.exit()

    def go_up(self):
        self.focus(self.current_cell_idx - 1)

    def go_down(self):
        self.focus(self.current_cell_idx + 1)
Example #3
0
class Notebook(Help, Format, KeyBindings):

    app: Optional[Application]
    layout: Layout
    copied_cell: Optional[Cell]
    console: Console
    _run_notebook_nb_path: str
    cells: List[Cell]
    executing_cells: Dict[int, Cell]
    json: Dict[str, Any]
    kd: Optional[KernelDriver]
    execution_count: int
    msg_id_2_execution_count: Dict[str, int]
    current_cell_idx: int
    top_cell_idx: int
    bottom_cell_idx: int
    lexer: Optional[PygmentsLexer] = PygmentsLexer(PythonLexer)
    language: str
    kernel_name: str
    no_kernel: bool
    dirty: bool
    quitting: bool
    kernel_cwd: Path

    def __init__(
        self,
        nb_path: Path,
        kernel_cwd: Path = Path("."),
        no_kernel: bool = False,
        save_path: Optional[Path] = None,
    ):
        self.nb_path = nb_path.resolve()
        self.kernel_cwd = kernel_cwd.resolve()
        os.chdir(self.kernel_cwd)
        self.app = None
        self.copied_cell = None
        self.console = Console()
        set_console(self.console)
        self.save_path = save_path
        self.no_kernel = no_kernel
        self.executing_cells = {}
        self.top_cell_idx = 0
        self.bottom_cell_idx = -1
        self.current_cell_idx = 0
        if self.nb_path.is_file():
            self.read_nb()
        else:
            self.create_nb()
        self.dirty = False
        self.quitting = False
        self.execution_count = 0
        self.msg_id_2_execution_count = {}
        self.edit_mode = False
        self.help_mode = False

    def set_language(self):
        self.kernel_name = self.json["metadata"]["kernelspec"]["name"]
        self.language = self.json["metadata"]["kernelspec"]["language"]
        if self.language == "python":
            self.lexer = PygmentsLexer(PythonLexer)
        elif self.language == "cpp":
            self.lexer = PygmentsLexer(CppLexer)
        else:
            self.lexer = None
        if self.no_kernel:
            self.kd = None
        else:
            try:
                self.kd = KernelDriver(kernel_name=self.kernel_name, log=False)
                kernel_driver.driver._output_hook_default = self.output_hook
            except RuntimeError:
                self.kd = None

    @property
    def current_cell(self):
        return self.cells[self.current_cell_idx]

    async def run_cell(self, idx: Optional[int] = None):
        if idx is None:
            idx = self.current_cell_idx
        self.focus(idx)
        await self.current_cell.run()

    async def run_all(self):
        await self.kd.start()
        for i in range(len(self.cells)):
            await self.run_cell(i)

    def show(self):
        self.key_bindings = PtKeyBindings()
        self.bind_keys()
        self.create_layout()
        self.app = Application(layout=self.layout,
                               key_bindings=self.key_bindings,
                               full_screen=True)
        self.focus(0)
        asyncio.run(self._show())

    def update_layout(self):
        if self.app:
            self.create_layout()
            self.app.layout = self.layout

    def create_layout(self):
        inout_cells = list(
            itertools.chain.from_iterable([
                (
                    VSplit([cell.input_prefix, cell.input]),
                    VSplit([cell.output_prefix, ONE_COL, cell.output,
                            ONE_COL]),
                )
                for cell in self.cells[self.top_cell_idx:self.bottom_cell_idx +
                                       1  # noqa
                                       ]
            ]))
        nb_window = ScrollablePane(HSplit(inout_cells), show_scrollbar=False)

        def get_top_bar_text():
            text = ""
            if self.dirty:
                text += "+ "
            text += str(self.nb_path.relative_to(self.kernel_cwd))
            if self.dirty and self.quitting:
                text += (
                    " (no write since last change, please exit again to confirm, "
                    "or save your changes)")
            return text

        def get_bottom_bar_text():
            text = ""
            if self.kd and not self.no_kernel and self.kernel_name:
                if self.executing_cells:
                    kernel_status = "busy"
                else:
                    kernel_status = "idle"
                text += f"{self.kernel_name} ({kernel_status})"
            else:
                text += "[NO KERNEL]"
            text += (
                f" @ {self.kernel_cwd} - {self.current_cell_idx + 1}/{len(self.cells)}"
            )
            return text

        self.top_bar = FormattedTextToolbar(get_top_bar_text,
                                            style="#ffffff bg:#444444")
        self.bottom_bar = FormattedTextToolbar(get_bottom_bar_text,
                                               style="#ffffff bg:#444444")
        root_container = HSplit([self.top_bar, nb_window, self.bottom_bar])
        self.layout = Layout(root_container)

    def focus(self,
              idx: int,
              update_layout: bool = False,
              no_change: bool = False):
        """
        Focus on a cell.

        Parameters
        ----------
        idx : int
            Index of the cell to focus on.
        update_layout : bool, optional
            If True, force the update of the layout. Default is False.
        no_change : bool optional
            If True, the cells didn't change. Default is False.
        """
        if 0 <= idx < len(self.cells):
            if self.app:
                if self.update_visible_cells(idx, no_change) or update_layout:
                    self.update_layout()
                self.app.layout.focus(self.cells[idx].input_window)
            self.current_cell_idx = idx

    def update_visible_cells(self, idx: int, no_change: bool) -> bool:
        self.app = cast(Application, self.app)
        size = self.app.renderer.output.get_size()
        available_height = size.rows - 2  # status bars
        if idx < self.top_cell_idx or self.bottom_cell_idx == -1:
            # scroll up
            (
                self.top_cell_idx,
                self.bottom_cell_idx,
            ) = self.get_visible_cell_idx_from_top(idx, available_height)
            return True
        if idx > self.bottom_cell_idx:
            # scroll down
            (
                self.top_cell_idx,
                self.bottom_cell_idx,
            ) = self.get_visible_cell_idx_from_bottom(idx, available_height)
            return True
        if no_change:
            return False
        # there might be less or more cells, or the cells' content may have changed
        top_cell_idx_keep, bottom_cell_idx_keep = (
            self.top_cell_idx,
            self.bottom_cell_idx,
        )
        while True:
            (
                self.top_cell_idx,
                self.bottom_cell_idx,
            ) = self.get_visible_cell_idx_from_top(self.top_cell_idx,
                                                   available_height)
            if self.top_cell_idx <= idx <= self.bottom_cell_idx:
                break
            self.top_cell_idx += 1
        return not (self.top_cell_idx == top_cell_idx_keep
                    and self.bottom_cell_idx == bottom_cell_idx_keep)

    def get_visible_cell_idx_from_top(
            self, idx: int, available_height: int) -> Tuple[int, int]:
        cell_nb = -1
        for cell in self.cells[idx:]:
            available_height -= cell.get_height()
            cell_nb += 1
            if available_height <= 0:
                break
        # bottom cell may be clipped by ScrollablePane
        return idx, idx + cell_nb

    def get_visible_cell_idx_from_bottom(
            self, idx: int, available_height: int) -> Tuple[int, int]:
        cell_nb = -1
        for cell in self.cells[idx::-1]:
            available_height -= cell.get_height()
            cell_nb += 1
            if available_height <= 0:
                break
        # top cell may be clipped by ScrollablePane
        return idx - cell_nb, idx

    def exit_cell(self):
        self.edit_mode = False
        self.current_cell.update_json()
        self.current_cell.set_input_readonly()

    def enter_cell(self):
        self.edit_mode = True
        self.current_cell.set_input_editable()

    def move_up(self):
        idx = self.current_cell_idx
        if idx > 0:
            self.dirty = True
            self.cells[idx -
                       1], self.cells[idx] = self.cells[idx], self.cells[idx -
                                                                         1]
            self.focus(idx - 1, update_layout=True)

    def move_down(self):
        idx = self.current_cell_idx
        if idx < len(self.cells) - 1:
            self.dirty = True
            self.cells[idx], self.cells[idx +
                                        1] = self.cells[idx +
                                                        1], self.cells[idx]
            self.focus(idx + 1, update_layout=True)

    def clear_output(self):
        self.current_cell.clear_output()

    def markdown_cell(self):
        self.current_cell.set_as_markdown()

    def code_cell(self):
        self.current_cell.set_as_code()

    def raw_cell(self):
        self.current_cell.set_as_raw()

    async def queue_run_cell(self, and_select_below: bool = False):
        if self.kd:
            cell = self.current_cell
            if and_select_below:
                if self.current_cell_idx == len(self.cells) - 1:
                    self.insert_cell(self.current_cell_idx + 1)
                self.focus(self.current_cell_idx + 1)
            await cell.run()

    def cut_cell(self, idx: Optional[int] = None):
        self.dirty = True
        if idx is None:
            idx = self.current_cell_idx
        self.copied_cell = self.cells.pop(idx)
        if not self.cells:
            self.cells = [Cell(self)]
        elif idx == len(self.cells):
            idx -= 1
        self.focus(idx, update_layout=True)

    def copy_cell(self, idx: Optional[int] = None):
        if idx is None:
            idx = self.current_cell_idx
        idx = self.current_cell_idx
        self.copied_cell = self.cells[idx]

    def paste_cell(self, idx: Optional[int] = None, below=False):
        if self.copied_cell is not None:
            self.dirty = True
            if idx is None:
                idx = self.current_cell_idx + below
            pasted_cell = self.copied_cell.copy()
            self.cells.insert(idx, pasted_cell)
            self.focus(idx, update_layout=True)

    def insert_cell(self, idx: Optional[int] = None, below=False):
        self.dirty = True
        if idx is None:
            idx = self.current_cell_idx + below
        self.cells.insert(idx, Cell(self))
        self.focus(idx, update_layout=True)

    def output_hook(self, msg: Dict[str, Any]):
        msg_id = msg["parent_header"]["msg_id"]
        execution_count = self.msg_id_2_execution_count[msg_id]
        msg_type = msg["header"]["msg_type"]
        content = msg["content"]
        outputs = self.executing_cells[execution_count].json["outputs"]
        if msg_type == "stream":
            if (not outputs) or (outputs[-1]["name"] != content["name"]):
                outputs.append({
                    "name": content["name"],
                    "output_type": msg_type,
                    "text": []
                })
            outputs[-1]["text"].append(content["text"])
        elif msg_type in ("display_data", "execute_result"):
            outputs.append({
                "data": {
                    "text/plain": [content["data"].get("text/plain", "")]
                },
                "execution_count": execution_count,
                "metadata": {},
                "output_type": msg_type,
            })
            text = rich_print(f"Out[{execution_count}]:", style="red", end="")
            self.executing_cells[
                execution_count].output_prefix.content = FormattedTextControl(
                    text=ANSI(text))
        elif msg_type == "error":
            outputs.append({
                "ename": content["ename"],
                "evalue": content["evalue"],
                "output_type": "error",
                "traceback": content["traceback"],
            })
        else:
            return
        text, height = get_output_text_and_height(outputs)
        self.executing_cells[
            execution_count].output.content = FormattedTextControl(text=text)
        height_keep = self.executing_cells[execution_count].output.height
        self.executing_cells[execution_count].output.height = height
        if self.app and height_keep != height:
            # height has changed
            self.focus(self.current_cell_idx, update_layout=True)
            self.app.invalidate()

    async def _show(self):
        if self.kd:
            asyncio.create_task(self.kd.start())
        await self.app.run_async()

    async def exit(self):
        if self.dirty and not self.quitting:
            self.quitting = True
            return
        if self.kd:
            await self.kd.stop()
        self.app.exit()

    def go_up(self):
        self.focus(self.current_cell_idx - 1, no_change=True)

    def go_down(self):
        self.focus(self.current_cell_idx + 1, no_change=True)
Example #4
0
class FullNodeUI:
    """
    Full node UI instance. Displays node state, blocks, and connections. Calls parent_close_cb
    when the full node is closed. Uses store, blockchain, and connections, to display relevant
    information. The UI is updated periodically.
    """
    def __init__(
        self,
        store: FullNodeStore,
        blockchain: Blockchain,
        server: ChiaServer,
        port: int,
        parent_close_cb: Callable,
    ):
        self.port: int = port
        self.store: FullNodeStore = store
        self.blockchain: Blockchain = blockchain
        self.node_server: ChiaServer = server
        self.connections: PeerConnections = server.global_connections
        self.logs: List[logging.LogRecord] = []
        self.app: Optional[Application] = None
        self.closed: bool = False
        self.num_blocks: int = 10
        self.num_top_block_pools: int = 5
        self.top_winners: List[Tuple[uint64, bytes32]] = []
        self.our_winners: List[Tuple[uint64, bytes32]] = []
        self.prev_route: str = "home/"
        self.route: str = "home/"
        self.focused: bool = False
        self.parent_close_cb = parent_close_cb
        self.kb = self.setup_keybindings()
        self.style = Style([("error", "#ff0044")])
        self.pool_pks: List[PublicKey] = []
        key_config_filename = os.path.join(ROOT_DIR, "config", "keys.yaml")
        if os.path.isfile(key_config_filename):
            config = safe_load(open(key_config_filename, "r"))

            self.pool_pks = [
                PrivateKey.from_bytes(bytes.fromhex(ce)).get_public_key()
                for ce in config["pool_sks"]
            ]

        self.draw_initial()
        self.app = Application(
            style=self.style,
            layout=self.layout,
            full_screen=True,
            key_bindings=self.kb,
            mouse_support=True,
        )

        self.closed = False
        self.update_ui_task = asyncio.get_running_loop().create_task(
            self.update_ui())
        self.update_data_task = asyncio.get_running_loop().create_task(
            self.update_data())

    def close(self):
        # Closes this instance of the UI
        if not self.closed:
            self.closed = True
            self.route = "home/"
            if self.app:
                self.app.exit(0)

    def stop(self):
        # Closes this instance of the UI, and call parent close, which closes
        # all other instances, and shuts down the full node.
        self.close()
        self.parent_close_cb()

    def setup_keybindings(self) -> KeyBindings:
        kb = KeyBindings()
        kb.add("tab")(focus_next)
        kb.add("s-tab")(focus_previous)
        kb.add("down")(focus_next)
        kb.add("up")(focus_previous)
        kb.add("right")(focus_next)
        kb.add("left")(focus_previous)

        @kb.add("c-c")
        def exit_(event):
            self.close()

        return kb

    def draw_initial(self):
        search_field = SearchToolbar()
        self.empty_row = TextArea(focusable=False, height=1)

        # home/
        self.loading_msg = Label(text=f"Initializing UI....")
        self.server_msg = Label(text=f"Server running on port {self.port}.")
        self.syncing = TextArea(focusable=False, height=1)
        self.current_heads_label = TextArea(focusable=False, height=1)
        self.lca_label = TextArea(focusable=False, height=1)
        self.difficulty_label = TextArea(focusable=False, height=1)
        self.ips_label = TextArea(focusable=False, height=1)
        self.total_iters_label = TextArea(focusable=False, height=2)
        self.con_rows = []
        self.displayed_cons = []
        self.latest_blocks: List[HeaderBlock] = []
        self.connections_msg = Label(text=f"Connections")
        self.connection_rows_vsplit = Window()
        self.add_connection_msg = Label(text=f"Add a connection ip:port")
        self.add_connection_field = TextArea(
            height=1,
            prompt=">>> ",
            style="class:input-field",
            multiline=False,
            wrap_lines=False,
            search_field=search_field,
        )
        self.add_connection_field.accept_handler = self.async_to_sync(
            self.add_connection)
        self.latest_blocks_msg = Label(text=f"Latest blocks")
        self.latest_blocks_labels = [
            Button(text="block") for _ in range(self.num_blocks)
        ]

        self.search_block_msg = Label(text=f"Search block by hash")
        self.search_block_field = TextArea(
            height=1,
            prompt=">>> ",
            style="class:input-field",
            multiline=False,
            wrap_lines=False,
            search_field=search_field,
        )
        self.search_block_field.accept_handler = self.async_to_sync(
            self.search_block)

        self.top_block_pools_msg = Label(text=f"Top block pools")
        self.top_block_pools_labels = [
            Label(text="Top block pool")
            for _ in range(self.num_top_block_pools)
        ]
        self.our_pools_msg = Label(text=f"Our pool winnings")
        self.our_pools_labels = [
            Label(text="Our winnings") for _ in range(len(self.pool_pks))
        ]

        self.close_ui_button = Button("Close UI", handler=self.close)
        self.quit_button = Button("Stop node and close UI", handler=self.stop)
        self.error_msg = Label(style="class:error", text=f"")

        # block/
        self.block_msg = Label(text=f"Block")
        self.block_label = TextArea(focusable=True,
                                    scrollbar=True,
                                    focus_on_click=True)
        self.back_button = Button(text="Back",
                                  handler=self.change_route_handler("home/"))
        self.challenge_msg = Label(text=f"Block Header")
        self.challenge = TextArea(focusable=False)

        body = HSplit([self.loading_msg, self.server_msg],
                      height=D(),
                      width=D())
        self.content = Frame(title="Chia Full Node", body=body)
        self.layout = Layout(VSplit([self.content], height=D(), width=D()))

    def change_route_handler(self, route):
        def change_route():
            self.prev_route = self.route
            self.route = route
            self.focused = False
            self.error_msg.text = ""

        return change_route

    def async_to_sync(self, coroutine):
        def inner(buff):
            asyncio.get_running_loop().create_task(coroutine(buff.text))

        return inner

    async def search_block(self, text: str):
        try:
            block = await self.store.get_block(bytes.fromhex(text))
        except ValueError:
            self.error_msg.text = "Enter a valid hex block hash"
            return
        if block is not None:
            self.change_route_handler(f"block/{text}")()
        else:
            self.error_msg.text = "Block not found"

    async def add_connection(self, text: str):
        if ":" not in text:
            self.error_msg.text = (
                "Enter a valid IP and port in the following format: 10.5.4.3:8000"
            )
            return
        else:
            ip, port = ":".join(text.split(":")[:-1]), text.split(":")[-1]
        target_node: PeerInfo = PeerInfo(ip, uint16(int(port)))
        log.error(f"Want to connect to {ip}, {port}")
        if not (await self.node_server.start_client(target_node, None)):
            self.error_msg.text = f"Failed to connect to {ip}:{port}"

    async def get_latest_blocks(self,
                                heads: List[HeaderBlock]) -> List[HeaderBlock]:
        added_blocks: List[HeaderBlock] = []
        while len(added_blocks) < self.num_blocks and len(heads) > 0:
            heads = sorted(heads, key=lambda b: b.height, reverse=True)
            max_block = heads[0]
            if max_block not in added_blocks:
                added_blocks.append(max_block)
            heads.remove(max_block)
            prev: Optional[HeaderBlock] = self.blockchain.header_blocks.get(
                max_block.prev_header_hash, None)
            if prev is not None:
                heads.append(prev)
        return added_blocks

    async def draw_home(self):
        connections = [c for c in self.connections.get_connections()]
        if collections.Counter(connections) != collections.Counter(
                self.displayed_cons):
            new_con_rows = []
            for con in connections:
                con_str = f"{NodeType(con.connection_type).name} {con.get_peername()} {con.node_id.hex()[:10]}..."
                con_label = Label(text=con_str)

                def disconnect(c):
                    def inner():
                        self.connections.close(c)
                        self.layout.focus(self.quit_button)

                    return inner

                disconnect_button = Button("Disconnect",
                                           handler=disconnect(con))
                row = VSplit([con_label, disconnect_button])
                new_con_rows.append(row)
            self.displayed_cons = connections
            self.con_rows = new_con_rows
            if len(self.con_rows) > 0:
                self.layout.focus(self.con_rows[0])
            else:
                self.layout.focus(self.quit_button)

        if len(self.con_rows):
            new_con_rows = HSplit(self.con_rows)
        else:
            new_con_rows = Window(width=D(), height=0)

        if await self.store.get_sync_mode():
            max_height = -1
            for _, block in await self.store.get_potential_tips_tuples():
                if block.height > max_height:
                    max_height = block.height

            if max_height >= 0:
                self.syncing.text = f"Syncing up to {max_height}"
            else:
                self.syncing.text = f"Syncing"
        else:
            self.syncing.text = "Not syncing"
        heads: List[HeaderBlock] = self.blockchain.get_current_tips()

        lca_block: FullBlock = self.blockchain.lca_block
        if lca_block.height > 0:
            difficulty = await self.blockchain.get_next_difficulty(
                lca_block.prev_header_hash)
            ips = await self.blockchain.get_next_ips(lca_block.prev_header_hash
                                                     )
        else:
            difficulty = await self.blockchain.get_next_difficulty(
                lca_block.header_hash)
            ips = await self.blockchain.get_next_ips(lca_block.header_hash)
        total_iters = lca_block.header_block.challenge.total_iters

        new_block_labels = []
        for i, b in enumerate(self.latest_blocks):
            self.latest_blocks_labels[i].text = (
                f"{b.height}:{b.header_hash}"
                f" {'LCA' if b.header_hash == lca_block.header_hash else ''}"
                f" {'TIP' if b.header_hash in [h.header_hash for h in heads] else ''}"
            )
            self.latest_blocks_labels[i].handler = self.change_route_handler(
                f"block/{b.header_hash}")
            new_block_labels.append(self.latest_blocks_labels[i])

        top_block_pools_labels = self.top_block_pools_labels
        if len(self.top_winners) > 0:
            new_top_block_pools_labels = []
            for i, (winnings, pk) in enumerate(self.top_winners):
                self.top_block_pools_labels[
                    i].text = f"Public key {pk.hex()}: {winnings/1000000000000} chias."
                new_top_block_pools_labels.append(
                    self.top_block_pools_labels[i])
            top_block_pools_labels = new_top_block_pools_labels

        our_pools_labels = self.our_pools_labels
        if len(self.our_winners) > 0:
            new_our_pools_labels = []
            for i, (winnings, pk) in enumerate(self.our_winners):
                self.our_pools_labels[
                    i].text = f"Public key {pk.hex()}: {winnings/(1000000000000)} chias."
                new_our_pools_labels.append(self.our_pools_labels[i])
            our_pools_labels = new_our_pools_labels

        self.lca_label.text = f"Current least common ancestor {lca_block.header_hash} height {lca_block.height}"
        self.current_heads_label.text = "Heights of tips: " + str(
            [h.height for h in heads])
        self.difficulty_label.text = f"Current difficuty: {difficulty}"
        self.ips_label.text = f"Current VDF iterations per second: {ips}"
        self.total_iters_label.text = f"Total iterations since genesis: {total_iters}"

        try:
            if not self.focused:
                self.layout.focus(self.close_ui_button)
                self.focused = True
        except ValueError:  # Not yet in layout
            pass
        return HSplit(
            [
                self.server_msg,
                self.syncing,
                self.lca_label,
                self.current_heads_label,
                self.difficulty_label,
                self.ips_label,
                self.total_iters_label,
                Window(height=1, char="-", style="class:line"),
                self.connections_msg,
                new_con_rows,
                Window(height=1, char="-", style="class:line"),
                self.add_connection_msg,
                self.add_connection_field,
                Window(height=1, char="-", style="class:line"),
                self.latest_blocks_msg,
                *new_block_labels,
                Window(height=1, char="-", style="class:line"),
                self.search_block_msg,
                self.search_block_field,
                Window(height=1, char="-", style="class:line"),
                self.top_block_pools_msg,
                *top_block_pools_labels,
                Window(height=1, char="-", style="class:line"),
                self.our_pools_msg,
                *our_pools_labels,
                Window(height=1, char="-", style="class:line"),
                self.close_ui_button,
                self.quit_button,
                self.error_msg,
            ],
            width=D(),
            height=D(),
        )

    async def draw_block(self):
        block_hash: str = self.route.split("block/")[1]
        async with self.store.lock:
            block: Optional[FullBlock] = await self.store.get_block(
                bytes32(bytes.fromhex(block_hash)))
        if block is not None:
            self.block_msg.text = f"Block {str(block.header_hash)}"
            if self.block_label.text != str(block):
                self.block_label.text = str(block)
        else:
            self.block_label.text = f"Block hash {block_hash} not found"
        try:
            if not self.focused:
                self.layout.focus(self.back_button)
                self.focused = True
        except ValueError:  # Not yet in layout
            pass
        return HSplit([self.block_msg, self.block_label, self.back_button],
                      width=D(),
                      height=D())

    async def update_ui(self):
        try:
            while not self.closed:
                if self.route.startswith("home/"):
                    self.content.body = await self.draw_home()
                elif self.route.startswith("block/"):
                    self.content.body = await self.draw_block()

                if self.app and not self.app.invalidated:
                    self.app.invalidate()
                await asyncio.sleep(0.25)
        except concurrent.futures._base.CancelledError as e:
            log.warn(f"Cancelled error in UI: {type(e)}: {e}")
        except Exception as e:
            log.warn(f"Exception in UI update_ui {type(e)}: {e}")
            raise e

    async def update_data(self):
        try:
            while not self.closed:
                heads: List[HeaderBlock] = self.blockchain.get_current_tips()
                self.latest_blocks = await self.get_latest_blocks(heads)

                header_block = heads[0]
                coin_balances = {
                    bytes(header_block.proof_of_space.pool_pubkey):
                    calculate_block_reward(header_block.height)
                }
                while header_block.height != 0:
                    header_block = self.blockchain.header_blocks[
                        header_block.prev_header_hash]
                    pool_pk = bytes(header_block.proof_of_space.pool_pubkey)
                    if pool_pk not in coin_balances:
                        coin_balances[pool_pk] = 0
                    coin_balances[pool_pk] += calculate_block_reward(
                        header_block.height)
                self.top_winners = sorted(
                    [(rewards, key) for key, rewards in coin_balances.items()],
                    reverse=True,
                )[:self.num_top_block_pools]

                self.our_winners = [
                    (coin_balances[bytes(pk)],
                     bytes(pk)) if bytes(pk) in coin_balances else
                    (0, bytes(pk)) for pk in self.pool_pks
                ]
                await asyncio.sleep(5)
        except concurrent.futures._base.CancelledError as e:
            log.warn(f"Cancelled error in UI: {type(e)}: {e}")
        except Exception as e:
            log.warn(f"Exception in UI update_data {type(e)}: {e}")
            raise e

    async def await_closed(self):
        await self.update_ui_task
        await self.update_data_task
Example #5
0
class PlayListEditor:
    def __init__(self, playlist: LocalFilePlaylist, editable=False) -> None:
        self.playlist: LocalFilePlaylist = playlist
        self.editable = False
        self.text_field = None
        self.application = None
        self.key_bindings = None
        self.layout = None
        self.play_info_dialog = None
        self.skip_info_dialog = None
        self.show_status_bar = True
        self.focus_index = 0

    def run(self):
        if self.application is not None:
            self.application.run()

    def is_show_status_bar(self):
        return self.show_status_bar

    def create_key_bindings(self):
        kb = KeyBindings()

        @kb.add('c-q')
        def _(event):
            self.exit()

        kb.add("tab")(focus_next)

        self.key_bindings = kb

    def create_content(self):

        # 文本编辑器
        text_editor = self.create_text_editor()

        # 播放列表属性编辑器
        property_editor = self.create_property_editor()

        body = HSplit([
            HSplit(
                [
                    property_editor,
                    Label(' '),
                    Window(height=1, char="-", style="class:line"),
                    text_editor,
                ],
                height=D(),
            ),
            ConditionalContainer(
                content=VSplit(
                    [
                        Window(FormattedTextControl(self.get_statusbar_text),
                               style="class:status"),
                        Window(
                            FormattedTextControl(
                                self.get_statusbar_right_text),
                            style="class:status.right",
                            width=9,
                            align=WindowAlign.RIGHT,
                        ),
                    ],
                    height=1,
                ),
                filter=Condition(lambda: self.is_show_status_bar()),
            ),
        ])

        self.create_key_bindings()

        root_container = MenuContainer(
            body=body,
            menu_items=[
                MenuItem(
                    "File",
                    children=[
                        MenuItem("Exit", handler=self.exit),
                    ],
                ),
            ],
            floats=[
                Float(
                    xcursor=True,
                    ycursor=True,
                    content=CompletionsMenu(max_height=16, scroll_offset=1),
                ),
            ],
            key_bindings=self.key_bindings,
        )

        style = Style.from_dict({
            "status": "reverse",
            "shadow": "bg:#440044",
        })

        self.layout = Layout(root_container, focused_element=self.text_field)

        self.application = Application(
            layout=self.layout,
            enable_page_navigation_bindings=True,
            style=style,
            mouse_support=True,
            full_screen=True,
        )

    def create_property_editor(self):

        current_file_path = self.playlist.media_list[
            self.playlist.current_index]
        _, current_file_name = os.path.split(current_file_path)

        self.play_info_dialog = Dialog(
            modal=False,
            title="播放记录",
            body=HSplit([
                Label(''),
                Label(' 播放文件'),
                Button(f'{current_file_name}', ),
                Label(' 播放位置'),
                Button(f'{self.playlist.current_pos}', ),
                Label(''),
            ],
                        width=38,
                        padding=1))
        self.skip_info_dialog = Dialog(
            modal=False,
            title="设置",
            body=HSplit([
                Label(''),
                Label(' 跳过片头'),
                Button(f'{self.playlist.skip_head}', ),
                Label(' 跳过片尾'),
                Button(f'{self.playlist.skip_tail}', ),
                Label(''),
            ],
                        width=38,
                        padding=1))

        left_window = VSplit([
            self.play_info_dialog,
            self.skip_info_dialog,
        ],
                             width=40,
                             padding=2)
        return left_window

    def create_text_editor(self):
        search_toolbar = SearchToolbar()
        text = ''
        for file_path in self.playlist.media_list:
            text += file_path + '\n'
        self.text_field = TextArea(
            text=text,
            read_only=True,
            #lexer=DynamicLexer(
            #    lambda: PygmentsLexer.from_filename(
            #        ApplicationState.current_path or ".txt", sync_from_start=False
            #    )
            #),
            scrollbar=True,
            line_numbers=True,
            search_field=search_toolbar,
        )
        text_editor = HSplit([self.text_field, search_toolbar])
        return text_editor

    def exit(self):
        if self.application.is_running:
            self.application.exit()

    def get_statusbar_text(self):
        return " Press ctrl-q to exit. "

    def get_statusbar_right_text(self):
        return " {}:{}  ".format(
            self.text_field.document.cursor_position_row + 1,
            self.text_field.document.cursor_position_col + 1,
        )
Example #6
0
class FullNodeUI:
    """
    Full node UI instance. Displays node state, blocks, and connections. Calls parent_close_cb
    when the full node is closed. Uses the RPC client to fetch data from a full node and to display relevant
    information. The UI is updated periodically.
    """
    def __init__(self, parent_close_cb: Callable, rpc_client: RpcClient):
        self.rpc_client = rpc_client
        self.app: Optional[Application] = None
        self.data_initialized = False
        self.block = None
        self.closed: bool = False
        self.num_blocks: int = 10
        self.num_top_block_pools: int = 10
        self.top_winners: List[Tuple[uint64, bytes32]] = []
        self.our_winners: List[Tuple[uint64, bytes32]] = []
        self.prev_route: str = "home/"
        self.route: str = "home/"
        self.focused: bool = False
        self.parent_close_cb = parent_close_cb
        self.kb = self.setup_keybindings()
        self.style = Style([("error", "#ff0044")])
        self.pool_pks: List[PublicKey] = []
        key_config_filename = os.path.join(ROOT_DIR, "config", "keys.yaml")
        if os.path.isfile(key_config_filename):
            config = safe_load(open(key_config_filename, "r"))

            self.pool_pks = [
                PrivateKey.from_bytes(bytes.fromhex(ce)).get_public_key()
                for ce in config["pool_sks"]
            ]

        self.draw_initial()
        self.app = Application(
            style=self.style,
            layout=self.layout,
            full_screen=True,
            key_bindings=self.kb,
            mouse_support=True,
        )

        self.closed = False
        self.update_ui_task = asyncio.get_running_loop().create_task(
            self.update_ui())
        self.update_data_task = asyncio.get_running_loop().create_task(
            self.update_data())

    def close(self):
        # Closes this instance of the UI
        if not self.closed:
            self.closed = True
            self.route = "home/"
            if self.app:
                self.app.exit(0)

    def stop(self):
        # Closes this instance of the UI, and call parent close, which closes
        # all other instances, and shuts down the full node.
        self.close()
        self.parent_close_cb(True)

    def setup_keybindings(self) -> KeyBindings:
        kb = KeyBindings()
        kb.add("tab")(focus_next)
        kb.add("s-tab")(focus_previous)
        kb.add("down")(focus_next)
        kb.add("up")(focus_previous)
        kb.add("right")(focus_next)
        kb.add("left")(focus_previous)

        @kb.add("c-c")
        def exit_(event):
            self.close()

        return kb

    def draw_initial(self):
        search_field = SearchToolbar()
        self.empty_row = TextArea(focusable=False, height=1)

        # home/
        self.loading_msg = Label(text=f"Initializing UI....")
        self.syncing = TextArea(focusable=False, height=1)
        self.current_heads_label = TextArea(focusable=False, height=1)
        self.lca_label = TextArea(focusable=False, height=1)
        self.difficulty_label = TextArea(focusable=False, height=1)
        self.ips_label = TextArea(focusable=False, height=1)
        self.total_iters_label = TextArea(focusable=False, height=2)
        self.con_rows = []
        self.displayed_cons = set()
        self.latest_blocks: List[HeaderBlock] = []
        self.connections_msg = Label(text=f"Connections")
        self.connection_rows_vsplit = Window()
        self.add_connection_msg = Label(text=f"Add a connection ip:port")
        self.add_connection_field = TextArea(
            height=1,
            prompt=">>> ",
            style="class:input-field",
            multiline=False,
            wrap_lines=False,
            search_field=search_field,
        )
        self.add_connection_field.accept_handler = self.async_to_sync(
            self.add_connection)
        self.latest_blocks_msg = Label(text=f"Latest blocks")
        self.latest_blocks_labels = [
            Button(text="block") for _ in range(self.num_blocks)
        ]

        self.search_block_msg = Label(text=f"Search block by hash")
        self.search_block_field = TextArea(
            height=1,
            prompt=">>> ",
            style="class:input-field",
            multiline=False,
            wrap_lines=False,
            search_field=search_field,
        )
        self.search_block_field.accept_handler = self.async_to_sync(
            self.search_block)

        self.top_block_pools_msg = Label(text=f"Top block pools")
        self.top_block_pools_labels = [
            Label(text="Top block pool")
            for _ in range(self.num_top_block_pools)
        ]
        self.our_pools_msg = Label(text=f"Our pool winnings")
        self.our_pools_labels = [
            Label(text="Our winnings") for _ in range(len(self.pool_pks))
        ]

        self.close_ui_button = Button("Close UI", handler=self.close)
        self.quit_button = Button("Stop node and close UI", handler=self.stop)
        self.error_msg = Label(style="class:error", text=f"")

        # block/
        self.block_msg = Label(text=f"Block")
        self.block_label = TextArea(focusable=True,
                                    scrollbar=True,
                                    focus_on_click=True)
        self.back_button = Button(text="Back",
                                  handler=self.change_route_handler("home/"))
        self.challenge_msg = Label(text=f"Block Header")
        self.challenge = TextArea(focusable=False)

        body = HSplit([self.loading_msg], height=D(), width=D())
        self.content = Frame(title="Chia Full Node", body=body)
        self.layout = Layout(VSplit([self.content], height=D(), width=D()))

    def change_route_handler(self, route):
        def change_route():
            self.prev_route = self.route
            self.route = route
            self.focused = False
            self.error_msg.text = ""

        return change_route

    def async_to_sync(self, coroutine):
        def inner(buff=None):
            if buff is None:
                asyncio.get_running_loop().create_task(coroutine())
            else:
                asyncio.get_running_loop().create_task(coroutine(buff.text))

        return inner

    async def search_block(self, text: str):
        try:
            block = await self.rpc_client.get_block(bytes.fromhex(text))
        except ValueError:
            self.error_msg.text = "Enter a valid hex block hash"
            return
        if block is not None:
            self.change_route_handler(f"block/{text}")()
        else:
            self.error_msg.text = "Block not found"

    async def add_connection(self, text: str):
        if ":" not in text:
            self.error_msg.text = (
                "Enter a valid IP and port in the following format: 10.5.4.3:8000"
            )
            return
        else:
            ip, port = ":".join(text.split(":")[:-1]), text.split(":")[-1]
        log.info(f"Want to connect to {ip}, {port}")
        try:
            await self.rpc_client.open_connection(ip, int(port))
        except BaseException:
            # TODO: catch right exception
            self.error_msg.text = f"Failed to connect to {ip}:{port}"

    async def get_latest_blocks(
            self, heads: List[SmallHeaderBlock]) -> List[SmallHeaderBlock]:
        added_blocks: List[SmallHeaderBlock] = []
        while len(added_blocks) < self.num_blocks and len(heads) > 0:
            heads = sorted(heads, key=lambda b: b.height, reverse=True)
            max_block = heads[0]
            if max_block not in added_blocks:
                added_blocks.append(max_block)
            heads.remove(max_block)
            prev: Optional[
                SmallHeaderBlock] = await self.rpc_client.get_header(
                    max_block.prev_header_hash)
            if prev is not None:
                heads.append(prev)
        return added_blocks

    async def draw_home(self):
        connections: List[Dict] = [c for c in self.connections]
        if set([con["node_id"] for con in connections]) != self.displayed_cons:
            new_con_rows = []
            for con in connections:
                con_str = (
                    f"{NodeType(con['type']).name} {con['peer_host']} {con['peer_port']}/{con['peer_server_port']}"
                    f" {con['node_id'].hex()[:10]}...")
                con_label = Label(text=con_str)

                def disconnect(c):
                    async def inner():
                        await self.rpc_client.close_connection(c["node_id"])
                        self.layout.focus(self.quit_button)

                    return inner

                disconnect_button = Button("Disconnect",
                                           handler=self.async_to_sync(
                                               disconnect(con)))
                row = VSplit([con_label, disconnect_button])
                new_con_rows.append(row)
            self.displayed_cons = set([con["node_id"] for con in connections])
            self.con_rows = new_con_rows
            if len(self.con_rows) > 0:
                self.layout.focus(self.con_rows[0])
            else:
                self.layout.focus(self.quit_button)

        if len(self.con_rows):
            new_con_rows = HSplit(self.con_rows)
        else:
            new_con_rows = Window(width=D(), height=0)

        if self.sync_mode:
            if self.max_height >= 0:
                self.syncing.text = f"Syncing up to {self.max_height}"
            else:
                self.syncing.text = f"Syncing"
        else:
            self.syncing.text = "Not syncing"

        total_iters = self.lca_block.challenge.total_iters

        new_block_labels = []
        for i, b in enumerate(self.latest_blocks):
            self.latest_blocks_labels[i].text = (
                f"{b.height}:{b.header_hash}"
                f" {'LCA' if b.header_hash == self.lca_block.header_hash else ''}"
                f" {'TIP' if b.header_hash in [h.header_hash for h in self.tips] else ''}"
            )
            self.latest_blocks_labels[i].handler = self.change_route_handler(
                f"block/{b.header_hash}")
            new_block_labels.append(self.latest_blocks_labels[i])

        top_block_pools_labels = self.top_block_pools_labels
        if len(self.top_winners) > 0:
            new_top_block_pools_labels = []
            for i, (winnings, pk) in enumerate(self.top_winners):
                self.top_block_pools_labels[
                    i].text = f"Public key {pk.hex()}: {winnings/1000000000000} chias."
                new_top_block_pools_labels.append(
                    self.top_block_pools_labels[i])
            top_block_pools_labels = new_top_block_pools_labels

        our_pools_labels = self.our_pools_labels
        if len(self.our_winners) > 0:
            new_our_pools_labels = []
            for i, (winnings, pk) in enumerate(self.our_winners):
                self.our_pools_labels[
                    i].text = f"Public key {pk.hex()}: {winnings/(1000000000000)} chias."
                new_our_pools_labels.append(self.our_pools_labels[i])
            our_pools_labels = new_our_pools_labels

        self.lca_label.text = (
            f"Current least common ancestor {self.lca_block.header_hash}"
            f" height {self.lca_block.height}")
        self.current_heads_label.text = "Heights of tips: " + str(
            [h.height for h in self.tips])
        self.difficulty_label.text = f"Current difficulty: {self.difficulty}"

        self.ips_label.text = f"Current VDF iterations per second: {self.ips}"
        self.total_iters_label.text = f"Total iterations since genesis: {total_iters}"

        try:
            if not self.focused:
                self.layout.focus(self.close_ui_button)
                self.focused = True
        except ValueError:  # Not yet in layout
            pass
        return HSplit(
            [
                self.syncing,
                self.lca_label,
                self.current_heads_label,
                self.difficulty_label,
                self.ips_label,
                self.total_iters_label,
                Window(height=1, char="-", style="class:line"),
                self.connections_msg,
                new_con_rows,
                Window(height=1, char="-", style="class:line"),
                self.add_connection_msg,
                self.add_connection_field,
                Window(height=1, char="-", style="class:line"),
                self.latest_blocks_msg,
                *new_block_labels,
                Window(height=1, char="-", style="class:line"),
                self.search_block_msg,
                self.search_block_field,
                Window(height=1, char="-", style="class:line"),
                self.top_block_pools_msg,
                *top_block_pools_labels,
                Window(height=1, char="-", style="class:line"),
                self.our_pools_msg,
                *our_pools_labels,
                Window(height=1, char="-", style="class:line"),
                self.close_ui_button,
                self.quit_button,
                self.error_msg,
            ],
            width=D(),
            height=D(),
        )

    async def draw_block(self):
        block_hash: str = self.route.split("block/")[1]
        if self.block is None or self.block.header_hash != bytes32(
                bytes.fromhex(block_hash)):
            self.block: Optional[FullBlock] = await self.rpc_client.get_block(
                bytes32(bytes.fromhex(block_hash)))
        if self.block is not None:
            self.block_msg.text = f"Block {str(self.block.header_hash)}"
            if self.block_label.text != str(self.block):
                self.block_label.text = str(self.block)
        else:
            self.block_label.text = f"Block hash {block_hash} not found"
        try:
            if not self.focused:
                self.layout.focus(self.back_button)
                self.focused = True
        except ValueError:  # Not yet in layout
            pass
        return HSplit([self.block_msg, self.block_label, self.back_button],
                      width=D(),
                      height=D())

    async def update_ui(self):
        try:
            while not self.closed:
                if self.data_initialized:
                    if self.route.startswith("home/"):
                        self.content.body = await self.draw_home()
                    elif self.route.startswith("block/"):
                        self.content.body = await self.draw_block()

                    if self.app and not self.app.invalidated:
                        self.app.invalidate()
                await asyncio.sleep(0.5)
        except Exception as e:
            log.error(f"Exception in UI update_ui {type(e)}: {e}")
            raise e

    async def update_data(self):
        self.data_initialized = False
        counter = 0
        try:
            while not self.closed:
                try:
                    blockchain_state = await self.rpc_client.get_blockchain_state(
                    )
                    self.lca_block = blockchain_state["lca"]
                    self.tips = blockchain_state["tips"]
                    self.difficulty = blockchain_state["difficulty"]
                    self.ips = blockchain_state["ips"]
                    self.sync_mode = blockchain_state["sync_mode"]
                    self.connections = await self.rpc_client.get_connections()
                    if self.sync_mode:
                        max_block = await self.rpc_client.get_heaviest_block_seen(
                        )
                        self.max_height = max_block.height

                    self.latest_blocks = await self.get_latest_blocks(self.tips
                                                                      )

                    self.data_initialized = True
                    if counter % 20 == 0:
                        # Only request balances periodically, since it's an expensive operation
                        coin_balances: Dict[
                            bytes,
                            uint64] = await self.rpc_client.get_pool_balances(
                            )
                        self.top_winners = sorted(
                            [(rewards, key)
                             for key, rewards in coin_balances.items()],
                            reverse=True,
                        )[:self.num_top_block_pools]

                        self.our_winners = [
                            (coin_balances[bytes(pk)],
                             bytes(pk)) if bytes(pk) in coin_balances else
                            (0, bytes(pk)) for pk in self.pool_pks
                        ]

                    counter += 1
                    await asyncio.sleep(5)
                except (
                        aiohttp.client_exceptions.ClientConnectorError,
                        aiohttp.client_exceptions.ServerConnectionError,
                ) as e:
                    log.warning(
                        f"Could not connect to full node. Is it running? {e}")
                    await asyncio.sleep(5)
        except Exception as e:
            log.error(f"Exception in UI update_data {type(e)}: {e}")
            raise e

    async def await_closed(self):
        await self.update_ui_task
        await self.update_data_task
Example #7
0
class UI(Client):
    def __init__(self, username: str, password: str):
        super().__init__(username, password, handle_data=DataFormat.ANSI)

        self.commands = []

        self.output_buffer = Buffer_()
        self.cursor_pos = 0

        self.chat_buffer = Buffer_()

        self.output = BufferControl(self.output_buffer,
                                    input_processors=[FormatText()],
                                    include_default_input_processors=True)

        self.chat = BufferControl(self.chat_buffer,
                                  input_processors=[FormatText()],
                                  include_default_input_processors=True)

        self.hide_ip = "--hide-ip" in sys.argv

        self.suggest = AutoSuggestFromLogs([
            CommandSuggest(),
        ])

        self.input = TextArea(height=1,
                              prompt=" >> ",
                              multiline=False,
                              wrap_lines=False,
                              accept_handler=self.accept,
                              auto_suggest=self.suggest,
                              dont_extend_width=True)

        self.host_ip = FormattedTextControl(ANSI(""))

        self.chat_float = Float(Frame(Window(self.chat, wrap_lines=True)),
                                right=1,
                                top=0,
                                width=40,
                                height=12,
                                hide_when_covering_content=True)

        self.text = ""
        self.chat_text = ""

        def set_frame_size(fn):
            def inner(*args):
                size = self.app.output.get_size()
                self.chat_float.width = size.columns // 3
                self.chat_float.height = size.rows // 2
                return fn(*args)

            return inner

        self.out_window = Window(self.output, wrap_lines=True)

        kb = KeyBindings()

        @kb.add('c-c')
        @kb.add('c-q')
        def _(_):
            self.app.exit()
            self._loop = False
            self.run_again = False

        @kb.add('c-i', filter=has_focus(self.input))
        def __(_):
            fut = self.suggest.get_suggestion_future(self.input.buffer,
                                                     self.input.document)
            text = self.input.text

            def set_input(fut_2):
                res = fut_2.result()
                if res is not None:
                    self.input.text = text + res.text
                    self.input.document = Document(self.input.text,
                                                   cursor_position=len(
                                                       self.input.text))

            fut.add_done_callback(set_input)

        @kb.add(Keys.ScrollUp)
        def sup(_):
            self.output_buffer.cursor_up(1)
            self.out_window._scroll_up()  # pylint: disable=protected-access

        @kb.add(Keys.ScrollDown)
        def sdown(_):
            self.output_buffer.cursor_down(1)
            self.out_window._scroll_down()  # pylint: disable=protected-access

        self.app = Application(
            layout=Layout(
                container=HSplit([
                    Frame(
                        FloatContainer(self.out_window,
                                       floats=[self.chat_float])),
                    Frame(
                        VSplit([
                            self.input,
                            Window(self.host_ip,
                                   align=WindowAlign.RIGHT,
                                   dont_extend_width=True)
                        ]))
                ]),
                focused_element=self.input,
            ),
            full_screen=True,
            mouse_support=True,
            enable_page_navigation_bindings=True,
            key_bindings=merge_key_bindings([kb]),
            paste_mode=True,
        )

        self.app._on_resize = set_frame_size(self.app._on_resize)  # pylint: disable=protected-access

        self.run_again = True
        self.loop = get_event_loop()
        self._loop = False

        self.own_pass = ""
        self.own_ip = ""
        self.current_ip = ""

        # patch_stdout()

    @classmethod
    def create(cls) -> 'UI':
        user = prompt("Username: "******"Password: "******"{Fore.BLUE}[BROADCAST]{Fore.RESET} {converted_color_codes(e.msg)}"
                )
            else:
                self.set_output(converted_color_codes(e.msg))

    def event_error(self, e: ErrorEvent):
        self.err(e.error)

    @staticmethod
    def sanitize(arg: str) -> ANSI:
        return to_formatted_text(ANSI(arg))

    def err(self, message: str):
        self.set_output(
            f"{Fore.RED}{converted_color_codes(message)}{Fore.RESET}")

    def set_chat_output(self, text: str):
        new_text = (self.chat_text + "\n" + text).strip()
        self.chat_buffer.set_text(new_text)
        self.chat_text = new_text.replace("\t", " ")

    def set_output(self, text: str):
        new_text = (self.text + "\n" + text).strip()
        self.output_buffer.set_text(new_text)
        self.text = new_text.replace("\t", " ")
        self.cursor_pos = len(self.text)

    def clear_chat(self):
        self.chat_buffer.set_text("")
        self.chat_text = ""

    def clear(self):
        self.output_buffer.set_text("")
        self.text = ""

    def accept(self, _):
        if self.input.text.strip():
            self.set_output(f"\n>> {self.input.text}")
            self.commands.append(self.input.text)
            self.suggest.last_command(self.input.text)
            self.input.text = ""

    async def get_host_data(self):
        data = await self.command("pass see -l")
        self.own_pass = data.msg
        sys_info = await self.command('specs -l')
        self.current_ip = self.own_ip = re.search(
            r"(?P<ip>\d{1,3}(\.\d{1,3}){3,4})", sys_info.msg).group("ip")

    def launch(self):
        colorama.init()
        use_asyncio_event_loop()
        patch_stdout()

        while self.run_again:
            self.run_again = False
            self._loop = True

            self.loop.create_task(self.start())
            try:
                self.loop.run_until_complete(
                    self.app.run_async().to_asyncio_future())
            except KeyboardInterrupt:
                if self.current_ip != self.own_ip:
                    self.loop.run_until_complete(self.command("dc"))

    async def event_ready(self):
        o_text = self.output_buffer.text

        await self.get_host_data()

        text = ((
            f"{Fore.YELLOW}{self.own_ip} - {self.own_pass}{Fore.RESET} "
            if self.own_ip == self.current_ip else
            f"{Fore.YELLOW}{self.current_ip} / {self.own_ip} - {self.own_pass}{Fore.RESET} "
        ) if not self.hide_ip else "")
        self.host_ip.text = self.sanitize(text)

        self.clear()

        self.set_output(o_text)

        macros = MacroHolder()

        while self._loop:
            # TODO: Update these when they change
            macros.macros["self"] = self.own_ip
            macros.macros["pass"] = self.own_pass

            while not self.commands:
                await asyncio.sleep(0.1)

            try:
                line = self.commands.pop(0).strip()
                line = macros.parse(line)
                command, *args = line.split()

                if command == "chats":
                    if len(args) == 1 and args[0] == "clear":
                        self.clear_chat()
                    else:
                        await self.command(" ".join([command, *args]))

                elif command == "macro":
                    if args and args[0] in ("add", "remove", "list"):
                        sub = args.pop(0)
                        if sub == "add":
                            if len(args) >= 2:
                                macros += args[0], " ".join(args[1:])
                            else:
                                self.set_output(
                                    "Usage: macro add <name> <value>")
                        elif sub == "remove":
                            if args:
                                macros -= args[0]
                            else:
                                self.set_output("Usage: macro remove <name>")
                        else:
                            self.set_output("Macros:")
                            for key in sorted(list(macros)):
                                self.set_output(f"${key} -> {macros[key]}")

                    else:
                        self.set_output("Usage: macro [add/remove/list] ...")

                elif command == "clear":
                    self.clear()

                elif command == "quit":
                    self._loop = False
                    self.run_again = False
                    self.stop()
                    self.app.exit()

                else:
                    await self.command(" ".join([command, *args]))

            except Exception as e:  # pylint: disable=broad-except
                self.err(repr(e))
Example #8
0
class Client:
    def __init__(self, url: str = "127.0.0.1", port: int = "9999"):
        self.communicator = Communicator(url, port)

        self.CMDIN = CommandInput(title="DLNest Command Line(F1)",
                                  onAccept=self.onCommandAccept)
        self.w1 = self.CMDIN.getWindow()

        self.DLOutput = ResultsOutput(routineTask=self.routineTaskDLOutput,
                                      title="DLNest Output (F2)",
                                      style="class:dlnest_output")
        self.w2 = self.DLOutput.getWindow()

        self.ANOutput = AnalyzeOutput(routineTask=self.routineTaskANOutput,
                                      title="Analyzer Output (F3)",
                                      style="class:analyzer_output")
        self.w3 = self.ANOutput.getWindow()
        self.analyzeTaskID = ""

        self.TaskInfo = TaskInfoShower(routineTask=self.routineTaskInfo,
                                       title="Tasks (F4)")
        self.w4 = self.TaskInfo.getWindow()

        self.DevicesInfo = DevicesInfoShower(
            routineTask=self.routineTaskDevices, title="Devices (F5)")
        self.w5 = self.DevicesInfo.getWindow()

        self.container_fat = HSplit(
            [self.w1,
             VSplit([self.w2, self.w3]),
             VSplit([self.w4, self.w5])])
        self.container_tall = HSplit(
            [self.w1, self.w2, self.w3, self.w4, self.w5])

        self.kb = KeyBindings()

        @self.kb.add('c-c')
        def exit_(event):
            event.app.exit()

        @self.kb.add('f1')
        def focus1(event):
            event.app.layout.focus(self.w1)

        @self.kb.add('f2')
        def focus2(event):
            event.app.layout.focus(self.w2)

        @self.kb.add('f3')
        def focus3(event):
            event.app.layout.focus(self.w3)

        @self.kb.add('f4')
        def focus4(event):
            event.app.layout.focus(self.w4)

        @self.kb.add('f5')
        def focus5(event):
            event.app.layout.focus(self.w5)

        self.style = Style.from_dict({
            "frame.border": "fg:#ffb6c1",
            "frame.title": "fg:#1ef0ff",
            "command_frame": "bg:#008b8b",
            "dlnest_output": "bg:#451a4a",
            "analyzer_output": "bg:#451a4a",
            "analyzer_info_label": "bg:#da70d6",
            "analyzer_info_text1": "bg:#3f3f00",
            "analyzer_info_text2": "bg:#ff00ff",
            "running_task_status": "bg:#a01010 bold",
            "running_task_id": "bg:#303030",
            "running_task_gpu": "bg:#556b2f",
            "running_task_des": "bg:#c71585",
            "running_task_time": "bg:#2e3b37",
            "pending_task_status": "bg:#1010a0 bold",
            "pending_task_id": "bg:#303030",
            "pending_task_gpu": "bg:#556b2f",
            "pending_task_des": "bg:#c71585",
            "pending_task_time": "bg:#2e3b37",
            "suspend_task_status": "bg:#10a010 bold",
            "suspend_task_id": "bg:#303030",
            "suspend_task_gpu": "bg:#556b2f",
            "suspend_task_des": "bg:#c71585",
            "suspend_task_time": "bg:#2e3b37",
            "task_info_shower": "bg:#008bc0",
            "devices_info_shower": "bg:#008bc0",
            "devices_id": "bg:#303030",
            "devices_status_valid": "bg:#3cb371 bold",
            "devices_status_break": "bg:#a01010 bold",
            "devices_free_memory": "bg:#556b2f",
            "devices_tasks": "bg:#c71585"
        })

        self.layout = Layout(self.container_fat, focused_element=self.w1)
        self.app = Application(key_bindings=self.kb,
                               layout=self.layout,
                               full_screen=True,
                               style=self.style)
        self.app._on_resize = self.on_resize

    def on_resize(self):
        cols, rows = os.get_terminal_size(0)
        focused_element = self.layout.current_window
        if cols >= 2 * rows:  # fat
            self.app.layout = Layout(self.container_fat,
                                     focused_element=focused_element)
        else:  # tall
            self.app.layout = Layout(self.container_tall,
                                     focused_element=focused_element)

        self.app.renderer.erase(leave_alternate_screen=False)
        self.app._request_absolute_cursor_position()
        self.app._redraw()

    def getApp(self):
        return self.app

    def onCommandAccept(self, s: str):
        commandWordList = s.split(" ")
        while "" in commandWordList:
            commandWordList.remove("")

        if commandWordList[0] == "watch":
            self.analyzeTaskID = commandWordList[1]
        elif commandWordList[0] == "withdraw":
            self.analyzeTaskID = ""

        if commandWordList[0] == "runExp":
            if len(commandWordList) != 3:
                if self.analyzeTaskID != "":
                    commandWordList = [
                        commandWordList[0], self.analyzeTaskID,
                        commandWordList[1]
                    ]
                else:
                    return

        ret = self.communicator.giveACommand(commandWordList)

        if commandWordList[0] == "del":
            if ret["status"] == "success" and commandWordList[
                    1] == self.analyzeTaskID:
                self.analyzeTaskID = ""

        if "exit" in ret:
            self.app.exit()

    def routineTaskDLOutput(self, obj):
        #for buffer fresh
        if not hasattr(obj, "_count_"):
            obj._count_ = 0

        outStyledDict = self.communicator.giveACommand(["showDL", "-s"])
        outPlainDict = self.communicator.giveACommand(["showDL"])
        if "text" in outStyledDict and "text" in outPlainDict:
            try:
                obj.lexer.styled_text = outStyledDict["text"]
                obj.shower.text = outPlainDict["text"]
            except Exception as e:
                pass

    def routineTaskANOutput(self, obj):
        #for buffer fresh
        if not hasattr(obj, "_count_"):
            obj._count_ = 0

        if self.analyzeTaskID == "":
            obj.lexer.styled_text = []
            obj.shower.text = ""
            obj.infoText.text = [("", "No valid analyzer task is running")]
            obj.infoWindow.width = 33
            return

        outStyledDict = self.communicator.giveACommand(
            ["showAN", "-t", self.analyzeTaskID, "-s"])
        outPlainDict = self.communicator.giveACommand(
            ["showAN", "-t", self.analyzeTaskID])
        if "text" in outStyledDict and "text" in outPlainDict:
            try:
                obj.lexer.styled_text = outStyledDict["text"]
                obj.shower.text = outPlainDict["text"]
                obj.infoText.text = [("class:analyzer_info_text1",
                                      self.analyzeTaskID)]
                obj.infoWindow.width = len(self.analyzeTaskID)
            except Exception as e:
                pass
        else:
            self.analyzeTaskID = ""

    def routineTaskInfo(self, obj):
        # for buffer fresh
        if not hasattr(obj, "_count_"):
            obj._count_ = 0

        r = self.communicator.giveACommand(["showTask"])
        if r["status"] != "success":
            obj.lexer.taskInfo = []
            obj.shower.text = obj.lexer.get_text()
            return
        taskInfo = r["info"]
        try:
            obj.lexer.taskInfo = taskInfo
            obj.shower.text = obj.lexer.get_text()
        except Exception as e:
            pass

    def routineTaskDevices(self, obj):
        # for buffer fresh
        if not hasattr(obj, "_count_"):
            obj._count_ = 0

        r = self.communicator.giveACommand(["showDevice"])
        if r["status"] != "success":
            obj.lexer.devicesInfo = []
            obj.shower.text = obj.lexer.get_text()
            return
        obj.lexer.devicesInfo = r["info"]
        try:
            obj.shower.text = obj.lexer.get_text()
        except Exception as e:
            pass
Example #9
0
class PybreakGui(Bdb):
    def __init__(self):
        super().__init__()
        self.paused = True
        self.app_thread = threading.Thread(target=self.start_gui, args=(asyncio.get_event_loop(),))
        self.frame_history = FrameHistory()

        self.search_toolbar = SearchToolbar(
            text_if_not_searching=[("class:not-searching", "Press '/' to start searching.")]
        )

        def get_view_file():
            if len(self.frame_history.history) > 0:
                return self.frame_history.hist_frame.filename
            return ".py"

        self.text_area = TextArea(
            lexer=DynamicLexer(
                lambda: PygmentsLexer.from_filename(
                    get_view_file(), sync_from_start=False
                )
            ),
            search_field=self.search_toolbar,
            scrollbar=True,
            line_numbers=True,
        )
        self.container = HSplit(
            [
                self.text_area,
                self.search_toolbar,
            ]
        )

        kb = KeyBindings()

        @kb.add("c-q")
        def _(event: KeyPressEvent):
            self.paused = False
            self._quit()

        @kb.add("c-n")
        def _(event):
            self.set_next(self.frame_history.exec_frame.raw_frame)
            self.paused = False  # allow another frame to be processed

        self.app = Application(
            full_screen=True,
            layout=Layout(container=self.container),
            key_bindings=kb,
        )
        self.app.loop = asyncio.get_event_loop()

    def start_gui(self, loop):
        asyncio.set_event_loop(loop)
        self.app.run()
        self.text_area.buffer.insert_text("HELLO WORLD")

    def start(self, frame):
        self.app_thread.start()
        super().set_trace(frame)

    def _quit(self):
        sys.settrace(None)
        self.quitting = True
        self.app.exit()

    def user_call(self, frame: types.FrameType, argument_list):
        # if self.stop_here(frame):
        # self.frame_history.append(frame)
        pass

    def user_line(self, frame: types.FrameType):
        """
        This method is called from dispatch_line() when either
        stop_here() or break_here() yields True.
        i.e. when we stop OR break at this line.
         * stop_here() yields true if the frame lies below the frame where
         debugging started on the call stack. i.e. it will be called for
         every line after we start debugging.
         * break_here() yields true only if there's a breakpoint for this
         line
        """
        self.text_area.buffer.insert_text(f"FRAME = {frame.f_code.co_filename}:{frame.f_lineno}")
        if self.stop_here(frame):
            self.text_area.buffer.insert_text(str(frame.f_code.co_filename) + str(frame.f_lineno) + "\n")
            self.frame_history.append(frame)

            while self.paused:
                time.sleep(.1)
            self.paused = True
Example #10
0
class View:
    def __init__(self, manager: mp.Manager, shared_state: dict, rpc_channel: mp.Queue):
        self.manager = manager
        self.shared_state = shared_state
        self.rpc_channel = rpc_channel

        self.fileman = Filemanager(self)
        self.fileman_visible = True

        self.toolbar = Toolbar(self)

        self.views = OrderedDict()  # type: Dict[str, SimpleView]

        self.app = Application(
            full_screen=True,
            mouse_support=True,
            color_depth=ColorDepth.DEPTH_24_BIT,
            clipboard=InMemoryClipboard(),
            enable_page_navigation_bindings=False,
            # key_bindings=get_filemanager_kb()
            layout=Layout(
                container=HSplit([
                    DynamicContainer(self._get_children),
                    DynamicContainer(lambda: self.toolbar)
                ]),
                # focused_element=(self.current_view or self.fileman).input_field,
            ),
        )

    def _get_children(self):
        children = ([self.fileman] if self.fileman_visible else []) \
            + list(self.views.values())
        return VSplit(
            children=children,
            padding_char=Border.VERTICAL,
            padding=1,
            padding_style='#ffff00'
        ) if len(children) > 0 else Window()  # VSplit([]) will raise Exception

    @property
    def current_view(self) -> SimpleView:
        return self.views.get(self.shared_state['focused_view'])

    def set_focus(self, view_id: str) -> None:
        self.shared_state['focused_view'] = view_id
        # When creating a new_view then set_focus will be called immediately after
        # This will create a race-condition between app.focus("LOADING...") and the
        # "LOADING..." component being replaced by the actually content (SimpleView
        # starts a thread that listen on the RPC channel, which often takes "a while"
        # so wait for the app to be ready (is_dirty != None) before calling set_focus:
        threading.Thread(target=self._set_focus, args=(view_id, )).start()

    def _set_focus(self, view_id: str) -> None:
        # Break if multiple threads are competing for focus:
        while self.shared_state['focused_view'] == view_id:
            if self.current_view.is_dirty is None:
                sleep(.1)
            else:
                self.app.layout.focus(self.current_view.input_field)
                break

    def new_view(self, file_path: str = None):
        channel = self.manager.Queue()
        self.rpc_channel.edit_request('new_view', {} if file_path is None else {'file_path': file_path}, channel)
        # Wait for 'view-id-X' identifier:
        view_id = channel.get()
        assert view_id not in self.shared_state['view_channels'], "Duplicate view_id: {} ({})".format(view_id, self.shared_state)
        self.shared_state['view_channels'][view_id] = channel
        self.views[view_id] = SimpleView(file_path, channel, view_id, self)
        self.set_focus(view_id)

    def close_view(self, view_id: str):
        self.rpc_channel.notify('close_view', {'view_id': view_id})
        self.shared_state['view_channels'][view_id].put(('kill', {}))
        self.views[view_id].thread.join()
        del self.shared_state['view_channels'][view_id]
        del self.views[view_id]
        if view_id == self.shared_state['focused_view']:
            self.shared_state['focused_view'] = None
            try:
                new_focused_view = list(self.views)[0]
                self.set_focus(new_focused_view)
            except IndexError:
                if self.fileman_visible:
                    self.app.layout.focus(self.fileman.input_field)
                else:
                    logging.debug("[View] Calling app.exit()")
                    self.app.exit()
Example #11
0
class ImbTui:
    def __init__(self):
        self.init_done = asyncio.Event()

    async def start_ui(self):
        kb = KeyBindings()

        @kb.add('escape')
        def _(event):
            for t in asyncio.Task.all_tasks():
                if 'Imb.main()' in str(t) and not t.done():
                    t.cancel()

        # Allow member functions to access this frame to allow switching screens
        self.app_frame = Frame(title='Opsani Intelligent Manifest Builder',
                               body=Window())
        self.app = Application(
            full_screen=True,
            key_bindings=kb,
            layout=Layout(HSplit([self.app_frame,
                                  Label('Press ESC to exit')])))
        self.init_done.set()
        await self.app.run_async(set_exception_handler=False)

    async def stop_ui(self):
        self.app.exit()

    async def prompt_yn(self,
                        title,
                        prompt,
                        disable_back=False,
                        allow_other=False,
                        other_button_text="Other"):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        def yes_handler():
            result.value = True
            input_done.set()

        def no_handler():
            result.value = False
            input_done.set()

        def back_handler():
            result.back_selected = True
            input_done.set()

        def other_handler():
            result.other_selected = True
            input_done.set()

        buttons = [
            Button(text="Yes", handler=yes_handler),
            Button(text="No", handler=no_handler)
        ]
        if not disable_back:
            buttons.append(Button(text="Back", handler=back_handler))

        if allow_other:
            buttons.append(
                Button(text=other_button_text, handler=other_handler))

        yn_dialog = Dialog(
            title=title,
            body=Window(FormattedTextControl(prompt),
                        height=1,
                        align=WindowAlign.CENTER),
            buttons=buttons,
            modal=False,
        )
        # disable a_reverse style applied to dialogs
        yn_dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            Window(),
            yn_dialog,
            Window(),
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    # prompt-toolkit supports line wrapping but it disregards breaking of words across lines
    # In cases where the text will span multiple lines, it should be divided up into an
    # array of prompt lines where each line should be short enough to fit on the screen
    async def prompt_ok(self, title, prompt: Union[str, Iterable[str]]):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        dialog_body = []
        if isinstance(prompt, str):
            dialog_body.append(
                Window(FormattedTextControl(prompt),
                       height=1,
                       align=WindowAlign.CENTER))
        else:
            for line in prompt:
                dialog_body.append(
                    Window(FormattedTextControl(line),
                           height=1,
                           align=WindowAlign.CENTER))

        def ok_handler():
            result.value = True
            input_done.set()

        def back_handler():
            result.back_selected = True
            input_done.set()

        ok_dialog = Dialog(
            title=title,
            body=HSplit(dialog_body),
            buttons=[
                Button(text="Ok", handler=ok_handler),
                Button(text="Back", handler=back_handler),
            ],
            modal=False,
        )
        # disable a_reverse style applied to dialogs
        ok_dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            Window(),
            ok_dialog,
            Window(),
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def long_prompt_text_input(self,
                                     title,
                                     prompt: Union[str, Iterable[str]],
                                     initial_text='',
                                     allow_other=False):
        'prompt for single text input with a multi-line prompt'
        result = ImbTuiResult()
        input_done = asyncio.Event()

        def accept(buf) -> bool:
            get_app().layout.focus(ok_button)
            return True  # Keep text.

        dialog_body = []
        if isinstance(prompt, str):
            dialog_body.append(
                Window(FormattedTextControl(prompt),
                       height=1,
                       align=WindowAlign.CENTER))
        else:
            for line in prompt:
                dialog_body.append(
                    Window(FormattedTextControl(line),
                           height=1,
                           align=WindowAlign.CENTER))

        text_field = TextArea(text=initial_text,
                              multiline=False,
                              accept_handler=accept)
        dialog_body.append(text_field)

        def ok_handler():
            result.value = text_field.text
            input_done.set()

        def back_handler():
            result.back_selected = True
            input_done.set()

        ok_button = Button(text='Ok', handler=ok_handler)
        dialog = Dialog(
            title=title,
            body=HSplit(dialog_body),
            buttons=[
                ok_button,
                Button(text="Back", handler=back_handler),
            ],
            modal=False,
        )
        # disable a_reverse style applied to dialogs
        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            Window(),
            dialog,
            Window(),
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def prompt_text_input(self,
                                title,
                                prompts,
                                allow_other=False,
                                other_button_text="Other",
                                ok_button_text="Ok"):
        result = ImbTuiResult()
        input_done = asyncio.Event()
        text_fields = []
        dialog_hsplit_content = []

        def accept(buf) -> bool:
            get_app().layout.focus(ok_button)
            return True  # Keep text.

        def ok_handler() -> None:
            if len(prompts) == 1:
                result.value = text_fields[0].text
            else:
                result.value = tuple(t.text for t in text_fields)
            input_done.set()

        def back_handler() -> None:
            result.back_selected = True
            input_done.set()

        def other_handler() -> None:
            if len(prompts) == 1:
                result.value = text_fields[0].text
            else:
                result.value = tuple(t.text for t in text_fields)
            result.other_selected = True
            input_done.set()

        ok_button = Button(text=ok_button_text, handler=ok_handler
                           )  # capture ref to allow accept handler to focus it
        buttons = [ok_button, Button(text='Back', handler=back_handler)]
        if allow_other:
            buttons.append(
                Button(text=other_button_text, handler=other_handler))

        for p in prompts:
            text_field = TextArea(text=p.get('initial_text', ''),
                                  multiline=False,
                                  accept_handler=accept)
            text_fields.append(text_field)
            dialog_hsplit_content.extend([
                Window(FormattedTextControl(text=p['prompt']),
                       align=WindowAlign.CENTER,
                       dont_extend_height=True), text_field
            ])

        dialog = Dialog(
            title=title,
            body=HSplit(
                dialog_hsplit_content,
                padding=Dimension(preferred=1, max=1),
            ),
            buttons=buttons,
            modal=False,
        )
        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            Window(),
            dialog,
            Window(),
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def prompt_multiline_text_input(self,
                                          title,
                                          prompt: Union[str, Iterable[str]],
                                          initial_text=''):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        dialog_body = []
        if isinstance(prompt, str):
            dialog_body.append(
                Window(FormattedTextControl(prompt),
                       height=1,
                       align=WindowAlign.CENTER))
        else:
            for line in prompt:
                dialog_body.append(
                    Window(FormattedTextControl(line),
                           height=1,
                           align=WindowAlign.CENTER))

        textfield = TextArea(text=initial_text, multiline=True, scrollbar=True)
        dialog_body.append(textfield)

        def ok_handler() -> None:
            result.value = textfield.text
            input_done.set()

        def back_handler() -> None:
            result.back_selected = True
            input_done.set()

        ok_button = Button(text='Ok', handler=ok_handler)
        back_button = Button(text='Back', handler=back_handler)

        dialog = Dialog(
            title=title,
            body=HSplit(dialog_body, padding=Dimension(preferred=1, max=1)),
            buttons=[ok_button, back_button],
            modal=False,
        )

        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            dialog,
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def prompt_multiline_text_output(self, title, text=''):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        def ok_handler() -> None:
            input_done.set()

        def back_handler() -> None:
            result.back_selected = True
            input_done.set()

        ok_button = Button(text='Ok', handler=ok_handler)
        back_button = Button(text='Back', handler=back_handler)

        dialog = Dialog(
            title=title,
            body=TextArea(text=text,
                          wrap_lines=True,
                          multiline=True,
                          scrollbar=True,
                          read_only=True),
            buttons=[ok_button, back_button],
            modal=False,
        )

        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            dialog,
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def prompt_radio_list(self, values, title, header, allow_other=True):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        def ok_handler() -> None:
            result.value = radio_list.current_value
            input_done.set()

        def back_handler() -> None:
            result.back_selected = True
            input_done.set()

        def other_handler() -> None:
            result.other_selected = True
            input_done.set()

        buttons = [
            Button(text='Ok', handler=ok_handler),
            Button(text='Back', handler=back_handler),
        ]
        if allow_other:
            buttons.append(Button(text='Other', handler=other_handler))

        radio_list = RadioList(list(enumerate(values)))
        dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=HTML("    <b>{}</b>".format(header)),
                      dont_extend_height=True),
                radio_list,
            ],
                        padding=1),
            buttons=buttons,
            modal=False,
        )
        # disable a_reverse style applied to dialogs
        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([
            # Window(FormattedTextControl(HTML('<b>Kubernetes Config</b>')), align=WindowAlign.CENTER, height=1), # TODO: screen header
            Window(),
            dialog,
            Window()
        ])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result

    async def prompt_check_list(self, values, title, header, allow_other=True):
        result = ImbTuiResult()
        input_done = asyncio.Event()

        def ok_handler() -> None:
            result.value = cb_list.current_values
            input_done.set()

        def back_handler() -> None:
            result.back_selected = True
            input_done.set()

        def other_handler() -> None:
            result.other_selected = True
            input_done.set()

        buttons = [
            Button(text='Ok', handler=ok_handler),
            Button(text='Back', handler=back_handler),
        ]
        if allow_other:
            buttons.append(Button(text='Other', handler=other_handler))

        cb_list = CheckboxList(list(enumerate(values)))
        dialog = Dialog(
            title=title,
            body=HSplit([
                Label(text=HTML("    <b>{}</b>".format(header)),
                      dont_extend_height=True),
                cb_list,
            ],
                        padding=1),
            buttons=buttons,
            modal=False,
        )
        # disable a_reverse style applied to dialogs
        dialog.container.container.content.style = ""
        self.app_frame.body = HSplit([Window(), dialog, Window()])
        self.app.invalidate()
        self.app.layout.focus(self.app_frame)
        await input_done.wait()
        return result