diff --git a/main.py b/main.py index 2796aec7..9a9536b1 100644 --- a/main.py +++ b/main.py @@ -1,16 +1,18 @@ import streamlit as st -from st_pages import Page, show_pages +from st_pages import Page, Section, show_pages, add_page_title from utils.st_utils import initialize_st_page - initialize_st_page(title="Hummingbot Dashboard", icon="πŸ“Š") show_pages( [ Page("main.py", "Hummingbot Dashboard", "πŸ“Š"), - Page("pages/strategy_performance/app.py", "Strategy Performance", "πŸš€"), + Section("Foundation Pages", "🏠"), Page("pages/bot_orchestration/app.py", "Bot Orchestration", "πŸ™"), + Page("pages/file_manager/app.py", "File Manager", "πŸ—‚"), + Section("Community Pages", "πŸ‘¨β€πŸ‘©β€πŸ‘§β€πŸ‘¦"), + Page("pages/strategy_performance/app.py", "Strategy Performance", "πŸš€"), Page("pages/backtest_manager/app.py", "Backtest Manager", "βš™οΈ"), Page("pages/candles_downloader/app.py", "Candles Downloader", "πŸ—‚"), Page("pages/db_inspector/app.py", "DB Inspector", "πŸ”"), diff --git a/pages/bot_orchestration/app.py b/pages/bot_orchestration/app.py index e8196b06..f40a09f3 100644 --- a/pages/bot_orchestration/app.py +++ b/pages/bot_orchestration/app.py @@ -13,12 +13,10 @@ from ui_components.bot_performance_card import BotPerformanceCard from ui_components.dashboard import Dashboard -from ui_components.editor import Editor from ui_components.exited_bot_card import ExitedBotCard -from ui_components.file_explorer import FileExplorer from utils.st_utils import initialize_st_page -initialize_st_page(title="Bot Orchestration", icon="πŸ™", initial_sidebar_state="collapsed") +initialize_st_page(title="Bot Orchestration", icon="πŸ™", initial_sidebar_state="expanded") if "is_broker_running" not in st.session_state: st.session_state.is_broker_running = False @@ -35,21 +33,14 @@ if "selected_strategy" not in st.session_state: st.session_state.selected_strategy = None -if "selected_file" not in st.session_state: - st.session_state.selected_file = "" - -if "editor_tabs" not in st.session_state: - st.session_state.editor_tabs = {} - - def manage_broker_container(): if st.session_state.is_broker_running: docker_manager.stop_container("hummingbot-broker") - with st.spinner('Stopping hummingbot broker... You are not going to be able to manage bots anymore.'): + with st.spinner('Stopping Hummingbot Broker... you will not going to be able to manage bots anymore.'): time.sleep(5) else: docker_manager.create_broker() - with st.spinner('Starting hummingbot broker... This process may take a few seconds'): + with st.spinner('Starting Hummingbot Broker... This process may take a few seconds'): time.sleep(20) @@ -106,109 +97,81 @@ def update_containers_info(docker_manager): docker_manager = DockerManager() -CARD_WIDTH = 4 +CARD_WIDTH = 6 CARD_HEIGHT = 3 +NUM_CARD_COLS = 2 if not docker_manager.is_docker_running(): st.warning("Docker is not running. Please start Docker and refresh the page.") st.stop() -orchestrate, manage = st.tabs(["Orchestrate", "Manage Files"]) update_containers_info(docker_manager) exited_containers = [container for container in docker_manager.get_exited_containers() if "broker" not in container] -def get_grid_positions(n_cards: int, cols: int = 3, card_width: int = 4, card_height: int = 3): +def get_grid_positions(n_cards: int, cols: int = NUM_CARD_COLS, card_width: int = CARD_HEIGHT, card_height: int = CARD_WIDTH): rows = n_cards // cols + 1 x_y = [(x * card_width, y * card_height) for x in range(cols) for y in range(rows)] return sorted(x_y, key=lambda x: (x[1], x[0])) -with orchestrate: - with elements("create_bot"): - with mui.Grid(container=True, spacing=4): - with mui.Grid(item=True, xs=6): - with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): - with mui.Grid(container=True, spacing=4): - with mui.Grid(item=True, xs=12): - mui.Typography("πŸš€ Create Bot", variant="h4") - with mui.Grid(item=True, xs=8): - mui.TextField(label="Bot Name", variant="outlined", onChange=lazy(sync("new_bot_name")), - sx={"width": "100%"}) - with mui.Grid(item=True, xs=4): - with mui.Button(onClick=launch_new_bot): - mui.icon.AddCircleOutline() - mui.Typography("Create") - with mui.Grid(item=True, xs=6): - with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): - with mui.Grid(container=True, spacing=4): - with mui.Grid(item=True, xs=12): - mui.Typography("πŸ™ Manage Broker", variant="h4") - with mui.Grid(item=True, xs=8): - mui.Typography("To control and monitor your bots you need to launch Hummingbot Broker." - "This component enables two-way communication with bots.") - with mui.Grid(item=True, xs=4): - button_text = "Stop Broker" if st.session_state.is_broker_running else "Start Broker" - color = "error" if st.session_state.is_broker_running else "success" - icon = mui.icon.Stop if st.session_state.is_broker_running else mui.icon.PlayCircle - with mui.Button(onClick=manage_broker_container, color=color): - icon() - mui.Typography(button_text) - - with elements("active_instances_board"): - with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): - mui.Typography("πŸ¦… Active Bots", variant="h4") - if st.session_state.is_broker_running: - quantity_of_active_bots = len(st.session_state.active_bots) - if quantity_of_active_bots > 0: - # TODO: Make layout configurable - grid_positions = get_grid_positions(n_cards=quantity_of_active_bots, cols=3, - card_width=CARD_WIDTH, card_height=CARD_HEIGHT) - active_instances_board = Dashboard() - for (bot, config), (x, y) in zip(st.session_state.active_bots.items(), grid_positions): - st.session_state.active_bots[bot]["bot_performance_card"] = BotPerformanceCard(active_instances_board, - x, y, - CARD_WIDTH, CARD_HEIGHT) - with active_instances_board(): - for bot, config in st.session_state.active_bots.items(): - st.session_state.active_bots[bot]["bot_performance_card"](config) - else: - mui.Alert("No active bots found. Please create a new bot.", severity="info", sx={"margin": "1rem"}) +with elements("create_bot"): + with mui.Grid(container=True, spacing=4): + with mui.Grid(item=True, xs=6): + with mui.Paper(style={"padding": "2rem"}, variant="outlined"): + with mui.Grid(container=True, spacing=4): + with mui.Grid(item=True, xs=12): + mui.Typography("πŸš€ Create Instance", variant="h4") + with mui.Grid(item=True, xs=8): + mui.TextField(label="Bot Name", variant="outlined", onChange=lazy(sync("new_bot_name")), + sx={"width": "100%"}) + with mui.Grid(item=True, xs=4): + with mui.Button(onClick=launch_new_bot, variant="contained", color="success"): + mui.icon.AddCircleOutline() + mui.Typography("Create") + with mui.Grid(item=True, xs=6): + with mui.Paper(style={"padding": "2rem"}, variant="outlined"): + with mui.Grid(container=True, spacing=4): + with mui.Grid(item=True, xs=12): + mui.Typography("πŸ™ Manage Broker", variant="h4") + with mui.Grid(item=True, xs=8): + mui.Typography("Hummingbot Broker helps you control and monitor your bot instances.") + with mui.Grid(item=True, xs=4): + button_text = "Stop Broker" if st.session_state.is_broker_running else "Start Broker" + color = "error" if st.session_state.is_broker_running else "success" + icon = mui.icon.Stop if st.session_state.is_broker_running else mui.icon.PlayCircle + with mui.Button(onClick=manage_broker_container, color=color, variant="contained"): + icon() + mui.Typography(button_text) + +with elements("active_instances_board"): + with mui.Paper(style={"padding": "2rem"}, variant="outlined"): + mui.Typography("πŸ¦… Active Instances", variant="h4") + if st.session_state.is_broker_running: + quantity_of_active_bots = len(st.session_state.active_bots) + if quantity_of_active_bots > 0: + # TODO: Make layout configurable + grid_positions = get_grid_positions(n_cards=quantity_of_active_bots, cols=NUM_CARD_COLS, + card_width=CARD_WIDTH, card_height=CARD_HEIGHT) + active_instances_board = Dashboard() + for (bot, config), (x, y) in zip(st.session_state.active_bots.items(), grid_positions): + st.session_state.active_bots[bot]["bot_performance_card"] = BotPerformanceCard(active_instances_board, + x, y, + CARD_WIDTH, CARD_HEIGHT) + with active_instances_board(): + for bot, config in st.session_state.active_bots.items(): + st.session_state.active_bots[bot]["bot_performance_card"](config) else: - mui.Alert("Please start the Hummingbot Broker to control your bots.", severity="warning", sx={"margin": "1rem"}) - with elements("stopped_instances_board"): - grid_positions = get_grid_positions(n_cards=len(exited_containers), cols=3, card_width=4, card_height=3) - exited_instances_board = Dashboard() - for exited_instance, (x, y) in zip(exited_containers, grid_positions): - st.session_state.exited_bots[exited_instance] = ExitedBotCard(exited_instances_board, x, y, - CARD_WIDTH, 1) - with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): - mui.Typography("πŸ’€ Stopped Bots", variant="h4") - with exited_instances_board(): - for bot, card in st.session_state.exited_bots.items(): - card(bot) - - -with manage: - if "w" not in st.session_state: - board = Dashboard() - w = SimpleNamespace( - dashboard=board, - file_explorer=FileExplorer(board, 0, 0, 3, 7), - editor=Editor(board, 4, 0, 9, 7), - ) - st.session_state.w = w - - else: - w = st.session_state.w - - for tab_name, content in st.session_state.editor_tabs.items(): - if tab_name not in w.editor._tabs: - w.editor.add_tab(tab_name, content["content"], content["language"], content["file_path"]) - - with elements("bot_config"): - with mui.Paper(elevation=3, style={"padding": "2rem"}, spacing=[2, 2], container=True): - mui.Typography("πŸ—‚Files Management", variant="h3", sx={"margin-bottom": "2rem"}) - event.Hotkey("ctrl+s", sync(), bindInputs=True, overrideDefault=True) - with w.dashboard(): - w.file_explorer() - w.editor() + mui.Alert("No active bots found. Please create a new bot.", severity="info", sx={"margin": "1rem"}) + else: + mui.Alert("Please start Hummingbot Broker to control your bots.", severity="warning", sx={"margin": "1rem"}) +with elements("stopped_instances_board"): + grid_positions = get_grid_positions(n_cards=len(exited_containers), cols=NUM_CARD_COLS, card_width=CARD_WIDTH, card_height=CARD_HEIGHT) + exited_instances_board = Dashboard() + for exited_instance, (x, y) in zip(exited_containers, grid_positions): + st.session_state.exited_bots[exited_instance] = ExitedBotCard(exited_instances_board, x, y, + CARD_WIDTH, 1) + with mui.Paper(style={"padding": "2rem"}, variant="outlined"): + mui.Typography("πŸ’€ Inactive Instances", variant="h4") + with exited_instances_board(): + for bot, card in st.session_state.exited_bots.items(): + card(bot) diff --git a/pages/file_manager/README.md b/pages/file_manager/README.md new file mode 100644 index 00000000..d82ed9e2 --- /dev/null +++ b/pages/file_manager/README.md @@ -0,0 +1,13 @@ +### Description + +This page helps you manage and edit script files to run with Hummingbot instances: + +- Selecting files +- Editing and saving files + +### Maintainers + +This page is maintained by Hummingbot Foundation: + +* [cardosfede](https://github.com/cardosfede) +* [fengtality](https://github.com/fengtality) diff --git a/pages/file_manager/app.py b/pages/file_manager/app.py new file mode 100644 index 00000000..4843ca8a --- /dev/null +++ b/pages/file_manager/app.py @@ -0,0 +1,44 @@ +import streamlit as st +from streamlit_elements import elements, mui, lazy, sync, event + +from types import SimpleNamespace + +import streamlit as st +from streamlit_elements import elements, mui, lazy, sync, event + +from ui_components.dashboard import Dashboard +from ui_components.file_explorer import FileExplorer +from ui_components.editor import Editor +from utils.st_utils import initialize_st_page + +initialize_st_page(title="File Manager", icon="πŸ—‚οΈ", initial_sidebar_state="expanded") + +if "editor_tabs" not in st.session_state: + st.session_state.editor_tabs = {} + +# TODO: Use this to save the current file selected +if "selected_file" not in st.session_state: + st.session_state.selected_file = "" + +if "w" not in st.session_state: + board = Dashboard() + w = SimpleNamespace( + dashboard=board, + file_explorer=FileExplorer(board, 0, 0, 3, 7), + editor=Editor(board, 4, 0, 9, 7), + ) + st.session_state.w = w +else: + w = st.session_state.w + +for tab_name, content in st.session_state.editor_tabs.items(): + if tab_name not in w.editor._tabs: + w.editor.add_tab(tab_name, content["content"], content["language"], content["file_path"]) + +with elements("bot_config"): + with mui.Paper(style={"padding": "2rem"}, variant="outlined"): + mui.Typography("Select a file and click ✏️ to edit it or πŸ—‘οΈ to delete it. Click πŸ’Ύ to save your changes.", variant="body1", sx={"margin-bottom": "2rem"}) + event.Hotkey("ctrl+s", sync(), bindInputs=True, overrideDefault=True) + with w.dashboard(): + w.file_explorer() + w.editor() diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index d5c3deea..6eaceea3 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -38,10 +38,10 @@ def __call__(self, bot_config: dict): st.session_state.active_bots[bot_name]["selected_strategy"] = strategies[0] with mui.Card(key=self._key, - sx={"display": "flex", "flexDirection": "column", "borderRadius": 3, "overflow": "hidden"}, + sx={"display": "flex", "flexDirection": "column", "borderRadius": 2, "overflow": "hidden"}, elevation=2): color = "green" if bot_config["is_running"] else "red" - subheader_message = "Running" if bot_config["is_running"] else "Stopped" + subheader_message = "Running " + st.session_state.active_bots[bot_name]["selected_strategy"] if bot_config["is_running"] else "Stopped" mui.CardHeader( title=bot_config["bot_name"], subheader=subheader_message, @@ -52,10 +52,16 @@ def __call__(self, bot_config: dict): ) if bot_config["is_running"]: with mui.CardContent(sx={"flex": 1}): - mui.Typography("Status:") - mui.Typography(bot_config["status"]) - mui.Typography("Trades:") - mui.Typography(str(bot_config["trades"])) + with mui.Paper(elevation=2, sx={"padding": 2, "marginBottom": 2}): + mui.Typography("Status", variant="h6") + mui.Typography(bot_config["status"]) + with mui.Accordion(sx={"padding": 2, "marginBottom": 2}): + with mui.AccordionSummary(expandIcon="β–Ό"): + mui.Typography("Trades" + "(" + str(len(bot_config["trades"])) + ")", variant="h6") + with mui.AccordionDetails(): + mui.Typography(str(bot_config["trades"])) + mui.Typography("Run the following command in Bash/Terminal to attach to the bot instance:") + mui.TextField(disabled=True, value="docker attach " + bot_name, sx={"width": "100%"}) else: with mui.CardContent(sx={"flex": 1}): @@ -69,12 +75,11 @@ def __call__(self, bot_config: dict): mui.MenuItem(script, value=script) for strategy in strategies: mui.MenuItem(strategy, value=strategy) - with mui.Grid(item=True, xs=4): - with mui.Button(onClick=lambda x: self.start_strategy(bot_name, bot_config["broker_client"])): + with mui.Button(onClick=lambda x: self.start_strategy(bot_name, bot_config["broker_client"]), variant="contained", color="success"): mui.icon.PlayCircle() mui.Typography("Start") - with mui.CardActions(disableSpacing=True): - with mui.Button(onClick=lambda: DockerManager().stop_container(bot_name)): - mui.icon.DeleteForever() - mui.Typography("Stop Container") + with mui.CardActions(): + with mui.Button(onClick=lambda: DockerManager().stop_container(bot_name), variant="contained", color="error"): + mui.icon.DeleteForever() + mui.Typography("Stop Instance") diff --git a/ui_components/exited_bot_card.py b/ui_components/exited_bot_card.py index 5b9f11d1..a701f3e3 100644 --- a/ui_components/exited_bot_card.py +++ b/ui_components/exited_bot_card.py @@ -29,10 +29,10 @@ def __call__(self, bot_name: str): className=self._draggable_class, ) - with mui.CardActions(disableSpacing=True): - with mui.Button(onClick=lambda: DockerManager().start_container(bot_name), color="success"): + with mui.CardActions(): + with mui.Button(onClick=lambda: DockerManager().start_container(bot_name), variant="contained", color="success", sx={"pr": "2"}): mui.icon.PlayCircle() mui.Typography("Start Container") - with mui.Button(onClick=lambda: self.remove_container(bot_name), color="error"): + with mui.Button(onClick=lambda: self.remove_container(bot_name), variant="contained", color="error"): mui.icon.DeleteForever() mui.Typography("Delete Container") diff --git a/ui_components/file_explorer.py b/ui_components/file_explorer.py index fc5e88e4..f0037061 100644 --- a/ui_components/file_explorer.py +++ b/ui_components/file_explorer.py @@ -40,7 +40,7 @@ def __call__(self): with mui.Grid(container=True, spacing=4, sx={"display": "flex", "alignItems": "center"}): with mui.Grid(item=True, xs=6, sx={"display": "flex", "alignItems": "center"}): mui.icon.Folder() - mui.Typography("File Explorer") + mui.Typography("/bot_configs/") with mui.Grid(item=True, xs=6, sx={"display": "flex", "justifyContent": "flex-end"}): mui.IconButton(mui.icon.Delete, onClick=self.delete_file, sx={"mx": 1}) mui.IconButton(mui.icon.Edit, onClick=self.edit_file, sx={"mx": 1}) diff --git a/utils/st_utils.py b/utils/st_utils.py index bdfdf728..52ff9518 100644 --- a/utils/st_utils.py +++ b/utils/st_utils.py @@ -3,6 +3,7 @@ import inspect import streamlit as st +from st_pages import add_page_title def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_state="auto"): @@ -12,9 +13,11 @@ def initialize_st_page(title: str, icon: str, layout="wide", initial_sidebar_sta layout=layout, initial_sidebar_state=initial_sidebar_state ) - st.title(f"{icon} {title}") + # st.title(f"{icon} {title}") caller_frame = inspect.currentframe().f_back + add_page_title(layout="wide") + current_directory = Path(os.path.dirname(inspect.getframeinfo(caller_frame).filename)) readme_path = current_directory / "README.md" with st.expander("About This Page"):