diff --git a/dolphie/Modules/TabManager.py b/dolphie/Modules/TabManager.py index 31cb254..5094347 100644 --- a/dolphie/Modules/TabManager.py +++ b/dolphie/Modules/TabManager.py @@ -47,7 +47,6 @@ class Tab: replicas_worker_timer: Timer = None replicas_worker_running: bool = False - topbar: TopBar = None main_container: VerticalScroll = None metric_graph_tabs: TabbedContent = None loading_indicator: LoadingIndicator = None @@ -117,27 +116,14 @@ async def disconnect(self, update_topbar: bool = True): self.main_container.display = False self.sparkline.display = False + self.loading_indicator.display = False self.replicas_title.update("") for member in self.dolphie.app.query(f".replica_container_{self.id}"): await member.remove() if update_topbar: - self.update_topbar(connection_status=ConnectionStatus.disconnected) - - def update_topbar(self, connection_status: ConnectionStatus): - dolphie = self.dolphie - - dolphie.connection_status = connection_status - - # Only update the topbar if we're on the active tab - if self.id == self.dolphie.app.tab.id: - if dolphie.connection_status: - self.topbar.connection_status = dolphie.connection_status - self.topbar.host = dolphie.mysql_host - else: - self.topbar.connection_status = None - self.topbar.host = "" + self.dolphie.app.tab_manager.update_topbar(tab=self, connection_status=ConnectionStatus.disconnected) def host_setup(self): dolphie = self.dolphie @@ -170,8 +156,6 @@ async def command_get_input(data): await asyncio.sleep(0.25) - self.loading_indicator.display = False - # If we're here because of a worker cancel error or manually disconnected, # we want to pre-populate the host/port if self.worker_cancel_error or dolphie.connection_status == ConnectionStatus.disconnected: @@ -200,12 +184,15 @@ def __init__(self, app: App, config: Config): self.app = app self.config = config + self.active_tab: Tab = None self.tabs: dict = {} self.tab_id_counter: int = 1 self.host_tabs = self.app.query_one("#host_tabs", TabbedContent) self.host_tabs.display = False + self.topbar = self.app.query_one(TopBar) + async def create_tab(self, tab_name: str, use_hostgroup: bool = False, switch_tab: bool = True) -> Tab: tab_id = self.tab_id_counter @@ -394,10 +381,9 @@ async def create_tab(self, tab_name: str, use_hostgroup: bool = False, switch_ta self.tabs[tab_id] = tab if tab.manual_tab_name: - self.rename_tab(tab_id, tab.manual_tab_name) + self.rename_tab(tab, tab.manual_tab_name) # Save references to the widgets in the tab - tab.topbar = self.app.query_one(TopBar) tab.main_container = self.app.query_one(f"#main_container_{tab.id}", VerticalScroll) tab.metric_graph_tabs = self.app.query_one(f"#metric_graph_tabs_{tab.id}", TabbedContent) tab.loading_indicator = self.app.query_one(f"#loading_indicator_{tab.id}", LoadingIndicator) @@ -450,6 +436,8 @@ async def create_tab(self, tab_name: str, use_hostgroup: bool = False, switch_ta # By default, hide all the panels tab.sparkline.display = False tab.main_container.display = False + tab.loading_indicator.display = False + for panel in tab.dolphie.panels.all(): self.app.query_one(f"#panel_{panel}_{tab.id}").display = False @@ -469,7 +457,6 @@ async def create_tab(self, tab_name: str, use_hostgroup: bool = False, switch_ta for switch in switches_to_toggle: switch.toggle() - # Switch to the new tab if switch_tab: self.switch_tab(tab_id) @@ -480,12 +467,10 @@ async def create_tab(self, tab_name: str, use_hostgroup: bool = False, switch_ta return tab - async def remove_tab(self, tab_id: int): - await self.host_tabs.remove_pane(f"tab_{self.get_tab(tab_id).id}") - - def rename_tab(self, tab_id: int, new_name: str = None): - tab = self.get_tab(tab_id) + async def remove_tab(self, tab: Tab): + await self.host_tabs.remove_pane(f"tab_{tab.id}") + def rename_tab(self, tab: Tab, new_name: str = None): if not new_name and not tab.manual_tab_name: # mysql_host is the full host:port string, we want to split & truncate it to 24 characters host = tab.dolphie.mysql_host.split(":")[0][:24] @@ -503,18 +488,20 @@ def rename_tab(self, tab_id: int, new_name: str = None): if new_name: tab.dolphie.tab_name = new_name tab.name = new_name - self.host_tabs.get_tab(f"tab_{tab_id}").label = new_name + self.host_tabs.get_tab(f"tab_{tab.id}").label = new_name def switch_tab(self, tab_id: int): tab = self.get_tab(tab_id) if not tab: return - self.app.tab = tab # Update the current tab variable for the app + # Update the active/current tab + self.active_tab = tab + # Switch to the new tab in the UI self.host_tabs.active = f"tab_{tab.id}" - tab.update_topbar(connection_status=tab.dolphie.connection_status) + self.update_topbar(tab=tab, connection_status=tab.dolphie.connection_status) def get_tab(self, id: int) -> Tab: if id in self.tabs: @@ -527,3 +514,17 @@ def get_all_tabs(self) -> list[Tab]: all_tabs.append(tab) return all_tabs + + def update_topbar(self, tab: Tab, connection_status: ConnectionStatus): + dolphie = tab.dolphie + + dolphie.connection_status = connection_status + + # Only update the topbar if we're on the active tab + if tab.id == self.active_tab.id: + if dolphie.connection_status: + self.topbar.connection_status = dolphie.connection_status + self.topbar.host = dolphie.mysql_host + else: + self.topbar.connection_status = None + self.topbar.host = "" diff --git a/dolphie/Panels/replication_panel.py b/dolphie/Panels/replication_panel.py index 487fe7f..46ca266 100644 --- a/dolphie/Panels/replication_panel.py +++ b/dolphie/Panels/replication_panel.py @@ -307,7 +307,7 @@ def create_replication_table(tab: Tab, dashboard_table=False, replica: Replica = if dashboard_table: replication_delay = "[dark_yellow](delayed)" else: - replication_delay = f"[dark_yellow]Delay[/dark_yellow]: {format_time(data['SQL_Delay'])}" + replication_delay = f"[dark_yellow]Delay[/dark_yellow] {format_time(data['SQL_Delay'])}" lag_source = f"Lag ({replica_sbm_source})" if replica_sbm_source else "Lag" if lag is None or data["Slave_SQL_Running"].lower() == "no": diff --git a/dolphie/app.py b/dolphie/app.py index 04fe5e7..4daa710 100755 --- a/dolphie/app.py +++ b/dolphie/app.py @@ -65,8 +65,6 @@ def __init__(self, config: Config): self.config = config - # This will be the currently selected tab - self.tab: Tab = None self.loading_hostgroups: bool = False theme = Theme({ @@ -103,12 +101,12 @@ async def run_worker_main(self, tab_id: int): dolphie = tab.dolphie try: if not dolphie.main_db_connection.is_connected(): - self.tab_manager.rename_tab(tab_id) # this will use dolphie.host instead of mysql_host - tab.update_topbar(connection_status=ConnectionStatus.connecting) + self.tab_manager.rename_tab(tab) # this will use dolphie.host instead of mysql_host + self.tab_manager.update_topbar(tab=tab, connection_status=ConnectionStatus.connecting) tab.loading_indicator.display = True dolphie.db_connect() - self.tab_manager.rename_tab(tab_id) + self.tab_manager.rename_tab(tab) dolphie.worker_start_time = datetime.now() dolphie.polling_latency = (dolphie.worker_start_time - dolphie.worker_previous_start_time).total_seconds() @@ -205,7 +203,6 @@ async def run_worker_main(self, tab_id: int): replication_status=dolphie.replication_status, replication_lag=dolphie.replica_lag, ) - except ManualException as exception: # This will set up the worker state change function below to trigger the # host setup modal with the error @@ -243,7 +240,7 @@ async def on_worker_state_changed(self, event: Worker.StateChanged): if self.loading_hostgroups: tab.loading_indicator.display = False - if self.tab.id != tab.id or self.loading_hostgroups: + if self.tab_manager.active_tab.id != tab.id or self.loading_hostgroups: self.notify( ( f"[b light_blue]{dolphie.host}:{dolphie.port}[/b light_blue]: " @@ -315,7 +312,7 @@ def refresh_screen(self, tab: Tab): loading_indicator.display = False self.layout_graphs(tab) - tab.update_topbar(connection_status=dolphie.connection_status) + self.tab_manager.update_topbar(tab=tab, connection_status=dolphie.connection_status) if not tab.main_container.display: tab.main_container.display = True @@ -382,7 +379,7 @@ def monitor_read_only_change(self, tab: Tab): ): self.app.notify(title="Read-only mode change", message=message, severity="warning", timeout=15) - tab.update_topbar(connection_status=formatted_ro_status) + self.tab_manager.update_topbar(tab=tab, connection_status=formatted_ro_status) dolphie.connection_status = formatted_ro_status @@ -408,8 +405,8 @@ async def on_mount(self): self.connect_as_hostgroup(self.config.hostgroup) else: await self.tab_manager.create_tab(tab_name="Initial Tab") - self.run_worker_main(self.tab.id) - self.run_worker_replicas(self.tab.id) + self.run_worker_main(self.tab_manager.active_tab.id) + self.run_worker_replicas(self.tab_manager.active_tab.id) self.check_for_new_version() @@ -429,20 +426,20 @@ def metric_tab_changed(self, event: TabbedContent.TabActivated): self.update_graphs(metric_instance_name) def update_graphs(self, tab_metric_instance_name): - if not self.tab or not self.tab.panel_graphs.display: + if not self.tab_manager.active_tab or not self.tab_manager.active_tab.panel_graphs.display: return - for metric_instance in self.tab.dolphie.metric_manager.metrics.__dict__.values(): + for metric_instance in self.tab_manager.active_tab.dolphie.metric_manager.metrics.__dict__.values(): if tab_metric_instance_name == metric_instance.tab_name: for graph_name in metric_instance.graphs: - getattr(self.tab, graph_name).render_graph(metric_instance) + getattr(self.tab_manager.active_tab, graph_name).render_graph(metric_instance) self.update_stats_label(tab_metric_instance_name) def update_stats_label(self, tab_metric_instance_name): stat_data = {} - for metric_instance in self.tab.dolphie.metric_manager.metrics.__dict__.values(): + for metric_instance in self.tab_manager.active_tab.dolphie.metric_manager.metrics.__dict__.values(): if hasattr(metric_instance, "tab_name") and metric_instance.tab_name == tab_metric_instance_name: number_format_func = MetricManager.get_number_format_function(metric_instance, color=True) for metric_data in metric_instance.__dict__.values(): @@ -452,18 +449,18 @@ def update_stats_label(self, tab_metric_instance_name): formatted_stat_data = " ".join( f"[b light_blue]{label}[/b light_blue] {value}" for label, value in stat_data.items() ) - getattr(self.tab, tab_metric_instance_name).update(formatted_stat_data) + getattr(self.tab_manager.active_tab, tab_metric_instance_name).update(formatted_stat_data) def toggle_panel(self, panel_name): - panel = self.app.query_one(f"#panel_{panel_name}_{self.tab.id}") + panel = self.app.query_one(f"#panel_{panel_name}_{self.tab_manager.active_tab.id}") new_display = not panel.display panel.display = new_display - setattr(getattr(self.tab.dolphie.panels, panel_name), "visible", new_display) + setattr(getattr(self.tab_manager.active_tab.dolphie.panels, panel_name), "visible", new_display) - if panel_name not in [self.tab.dolphie.panels.graphs.name]: - self.app.refresh_panel(self.tab, panel_name, toggled=True) + if panel_name not in [self.tab_manager.active_tab.dolphie.panels.graphs.name]: + self.app.refresh_panel(self.tab_manager.active_tab, panel_name, toggled=True) def refresh_panel(self, tab: Tab, panel_name: str, toggled: bool = False): # If loading indicator is displaying, don't refresh @@ -535,7 +532,7 @@ def switch_changed(self, event: Switch.Changed): metric_instance_name = event.switch.name metric = event.switch.id - metric_instance = getattr(self.tab.dolphie.metric_manager.metrics, metric_instance_name) + metric_instance = getattr(self.tab_manager.active_tab.dolphie.metric_manager.metrics, metric_instance_name) metric_data: MetricManager.MetricData = getattr(metric_instance, metric) metric_data.visible = event.value @@ -548,7 +545,7 @@ async def on_key(self, event: events.Key): await self.capture_key(event.key) async def capture_key(self, key): - tab = self.tab_manager.get_tab(self.tab.id) + tab = self.tab_manager.active_tab if not tab: return @@ -594,7 +591,7 @@ async def capture_key(self, key): self.toggle_panel(dolphie.panels.dashboard.name) elif key == "2": self.toggle_panel(dolphie.panels.processlist.name) - self.tab.processlist_datatable.clear() + self.tab_manager.active_tab.processlist_datatable.clear() elif key == "3": self.toggle_panel(dolphie.panels.replication.name) @@ -620,7 +617,7 @@ async def capture_key(self, key): return self.toggle_panel(dolphie.panels.locks.name) - self.tab.locks_datatable.clear() + self.tab_manager.active_tab.locks_datatable.clear() elif key == "6": if not dolphie.is_mysql_version_at_least("5.7"): self.notify("DDL panel requires MySQL 5.7+") @@ -635,21 +632,21 @@ async def capture_key(self, key): return self.toggle_panel(dolphie.panels.ddl.name) - self.tab.ddl_datatable.clear() + self.tab_manager.active_tab.ddl_datatable.clear() elif key == "grave_accent": - self.tab.host_setup() + self.tab_manager.active_tab.host_setup() elif key == "space": if tab.worker.state != WorkerState.RUNNING: tab.worker_timer.stop() self.run_worker_main(tab.id) elif key == "plus": await self.tab_manager.create_tab(tab_name="New Tab") - self.tab.topbar.host = "" - self.tab.host_setup() + self.tab_manager.topbar.host = "" + self.tab_manager.active_tab.host_setup() elif key == "equals_sign": def command_get_input(tab_name): - self.tab_manager.rename_tab(tab.id, tab_name) + self.tab_manager.rename_tab(tab, tab_name) self.app.push_screen( CommandModal(command=HotkeyCommands.rename_tab, message="What would you like to rename the tab to?"), @@ -659,7 +656,7 @@ def command_get_input(tab_name): if len(self.tab_manager.tabs) == 1: self.notify("Removing all tabs is not permitted", severity="error") else: - await self.tab_manager.remove_tab(tab.id) + await self.tab_manager.remove_tab(tab) await tab.disconnect(update_topbar=False) self.notify(f"Tab [highlight]{tab.name}[/highlight] [white]has been removed", severity="success") @@ -1075,7 +1072,7 @@ def command_get_input(input_variable): @work(thread=True) def run_command_in_worker(self, key: str, dolphie: Dolphie, additional_data=None): - tab = self.tab_manager.get_tab(self.tab.id) + tab = self.tab_manager.active_tab # These are the screens to display we use for the commands def show_command_screen():