diff --git a/src/hosts/core/manager.py b/src/hosts/core/manager.py index 9e49fc5..e3951e4 100644 --- a/src/hosts/core/manager.py +++ b/src/hosts/core/manager.py @@ -331,15 +331,19 @@ class HostsManager: # Remove the entry deleted_entry = hosts_file.entries.pop(index) - canonical_hostname = deleted_entry.hostnames[0] if deleted_entry.hostnames else deleted_entry.ip_address - + canonical_hostname = ( + deleted_entry.hostnames[0] + if deleted_entry.hostnames + else deleted_entry.ip_address + ) + # Save the file immediately save_success, save_message = self.save_hosts_file(hosts_file) if not save_success: # If save fails, restore the entry hosts_file.entries.insert(index, deleted_entry) return False, f"Failed to save after deletion: {save_message}" - + return True, f"Entry deleted: {canonical_hostname}" except Exception as e: diff --git a/src/hosts/tui/app.py b/src/hosts/tui/app.py index f2de666..c9cad8d 100644 --- a/src/hosts/tui/app.py +++ b/src/hosts/tui/app.py @@ -91,12 +91,30 @@ class HostsManagerApp(App): # Right pane - entry details or edit form with Vertical(classes="common-pane right-pane") as right_pane: right_pane.border_title = "Entry Details" - yield DataTable( - id="entry-details-table", - show_header=False, - show_cursor=False, - disabled=True, - ) + + # Details display form (disabled inputs) + with Vertical(id="entry-details-display"): + yield Label("IP Address:") + yield Input( + placeholder="No entry selected", + id="details-ip-input", + disabled=True, + ) + yield Label("Hostnames (comma-separated):") + yield Input( + placeholder="No entry selected", + id="details-hostname-input", + disabled=True, + ) + yield Label("Comment:") + yield Input( + placeholder="No entry selected", + id="details-comment-input", + disabled=True, + ) + yield Checkbox( + "Active", id="details-active-checkbox", disabled=True + ) # Edit form (initially hidden) with Vertical(id="entry-edit-form", classes="hidden"): @@ -150,7 +168,7 @@ class HostsManagerApp(App): # Skip tuple-style bindings and only process Binding objects if not hasattr(binding, "show"): continue - + # Only show bindings marked with show=True if binding.show: # Get the display key diff --git a/src/hosts/tui/custom_footer.py b/src/hosts/tui/custom_footer.py index a2f46ad..bbc2d41 100644 --- a/src/hosts/tui/custom_footer.py +++ b/src/hosts/tui/custom_footer.py @@ -169,7 +169,7 @@ class CustomFooter(Widget): def add_left_item_old(self, item: str) -> None: """Backward compatibility method.""" self.add_left_item_legacy(item) - + def add_right_item_old(self, item: str) -> None: """Backward compatibility method.""" self.add_right_item_legacy(item) diff --git a/src/hosts/tui/details_handler.py b/src/hosts/tui/details_handler.py index c3c4102..b91c37b 100644 --- a/src/hosts/tui/details_handler.py +++ b/src/hosts/tui/details_handler.py @@ -5,7 +5,7 @@ This module handles the display and updating of entry details and edit forms in the right pane. """ -from textual.widgets import Input, Checkbox, DataTable +from textual.widgets import Input, Checkbox class DetailsHandler: @@ -23,30 +23,41 @@ class DetailsHandler: self.update_details_display() def update_details_display(self) -> None: - """Update the details display using a DataTable with labeled rows.""" - details_table = self.app.query_one("#entry-details-table", DataTable) + """Update the details display using disabled Input widgets.""" + details_display = self.app.query_one("#entry-details-display") edit_form = self.app.query_one("#entry-edit-form") - # Show details table, hide edit form - details_table.remove_class("hidden") + # Show details display, hide edit form + details_display.remove_class("hidden") edit_form.add_class("hidden") - # Clear existing data - details_table.clear() + # Get the input widgets + ip_input = self.app.query_one("#details-ip-input", Input) + hostname_input = self.app.query_one("#details-hostname-input", Input) + comment_input = self.app.query_one("#details-comment-input", Input) + active_checkbox = self.app.query_one("#details-active-checkbox", Checkbox) if not self.app.hosts_file.entries: - # Show empty message in a single row - if not details_table.columns: - details_table.add_column("Field", key="field") - details_table.add_row("No entries loaded") + # Show empty message + ip_input.value = "" + ip_input.placeholder = "No entries loaded" + hostname_input.value = "" + hostname_input.placeholder = "No entries loaded" + comment_input.value = "" + comment_input.placeholder = "No entries loaded" + active_checkbox.value = False return # Get visible entries to check if we need to adjust selection visible_entries = self.app.table_handler.get_visible_entries() if not visible_entries: - if not details_table.columns: - details_table.add_column("Field", key="field") - details_table.add_row("No visible entries") + ip_input.value = "" + ip_input.placeholder = "No visible entries" + hostname_input.value = "" + hostname_input.placeholder = "No visible entries" + comment_input.value = "" + comment_input.placeholder = "No visible entries" + active_checkbox.value = False return # If default entries are hidden and selected_entry_index points to a hidden entry, @@ -73,36 +84,28 @@ class DetailsHandler: entry = self.app.hosts_file.entries[self.app.selected_entry_index] - # Add columns for labeled rows (Field, Value) - only if not already present - if not details_table.columns: - details_table.add_column("Field", key="field") - details_table.add_column("Value", key="value") + # Update the input widgets with entry data + ip_input.value = entry.ip_address + ip_input.placeholder = "" + hostname_input.value = ", ".join(entry.hostnames) + hostname_input.placeholder = "" + comment_input.value = entry.comment or "" + comment_input.placeholder = "No comment" + active_checkbox.value = entry.is_active - # Add rows in the same order as edit form - details_table.add_row("IP Address", entry.ip_address, key="ip") - details_table.add_row("Hostnames", ", ".join(entry.hostnames), key="hostnames") - details_table.add_row("Comment", entry.comment or "", key="comment") - details_table.add_row( - "Active", "Yes" if entry.is_active else "No", key="active" - ) - - # Add DNS name if present (not in edit form but good to show) - if entry.dns_name: - details_table.add_row("DNS Name", entry.dns_name, key="dns") - - # Add notice for default system entries + # For default entries, show warning in placeholder text if entry.is_default_entry(): - details_table.add_row("", "", key="spacer") - details_table.add_row("⚠️ WARNING", "SYSTEM DEFAULT ENTRY", key="warning") - details_table.add_row("Note", "This entry cannot be modified", key="note") + ip_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified" + hostname_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified" + comment_input.placeholder = "⚠️ SYSTEM DEFAULT ENTRY - Cannot be modified" def update_edit_form(self) -> None: """Update the edit form with current entry values.""" - details_table = self.app.query_one("#entry-details-table", DataTable) + details_display = self.app.query_one("#entry-details-display") edit_form = self.app.query_one("#entry-edit-form") - # Hide details table, show edit form - details_table.add_class("hidden") + # Hide details display, show edit form + details_display.add_class("hidden") edit_form.remove_class("hidden") if not self.app.hosts_file.entries or self.app.selected_entry_index >= len( diff --git a/tests/test_main.py b/tests/test_main.py index 6dcfc68..f3389ed 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -156,16 +156,27 @@ class TestHostsManagerApp: ): app = HostsManagerApp() - # Mock the query_one method to return DataTable mock - mock_details_table = Mock() - mock_details_table.columns = [] # Mock empty columns list + # Mock the query_one method to return disabled input widgets + mock_details_display = Mock() mock_edit_form = Mock() + mock_ip_input = Mock() + mock_hostname_input = Mock() + mock_comment_input = Mock() + mock_active_checkbox = Mock() def mock_query_one(selector, widget_type=None): - if selector == "#entry-details-table": - return mock_details_table + if selector == "#entry-details-display": + return mock_details_display elif selector == "#entry-edit-form": return mock_edit_form + elif selector == "#details-ip-input": + return mock_ip_input + elif selector == "#details-hostname-input": + return mock_hostname_input + elif selector == "#details-comment-input": + return mock_comment_input + elif selector == "#details-active-checkbox": + return mock_active_checkbox return Mock() app.query_one = mock_query_one @@ -182,12 +193,13 @@ class TestHostsManagerApp: app.update_entry_details() - # Verify DataTable operations were called - mock_details_table.remove_class.assert_called_with("hidden") + # Verify input widgets were updated with entry data + mock_details_display.remove_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden") - mock_details_table.clear.assert_called_once() - mock_details_table.add_column.assert_called() - mock_details_table.add_row.assert_called() + assert mock_ip_input.value == "127.0.0.1" + assert mock_hostname_input.value == "localhost, local" + assert mock_comment_input.value == "Test comment" + assert mock_active_checkbox.value def test_update_entry_details_no_entries(self): """Test updating entry details with no entries.""" @@ -200,16 +212,27 @@ class TestHostsManagerApp: ): app = HostsManagerApp() - # Mock the query_one method to return DataTable mock - mock_details_table = Mock() - mock_details_table.columns = [] # Mock empty columns list + # Mock the query_one method to return disabled input widgets + mock_details_display = Mock() mock_edit_form = Mock() + mock_ip_input = Mock() + mock_hostname_input = Mock() + mock_comment_input = Mock() + mock_active_checkbox = Mock() def mock_query_one(selector, widget_type=None): - if selector == "#entry-details-table": - return mock_details_table + if selector == "#entry-details-display": + return mock_details_display elif selector == "#entry-edit-form": return mock_edit_form + elif selector == "#details-ip-input": + return mock_ip_input + elif selector == "#details-hostname-input": + return mock_hostname_input + elif selector == "#details-comment-input": + return mock_comment_input + elif selector == "#details-active-checkbox": + return mock_active_checkbox return Mock() app.query_one = mock_query_one @@ -217,12 +240,16 @@ class TestHostsManagerApp: app.update_entry_details() - # Verify DataTable operations were called for empty state - mock_details_table.remove_class.assert_called_with("hidden") + # Verify widgets show empty state placeholders + mock_details_display.remove_class.assert_called_with("hidden") mock_edit_form.add_class.assert_called_with("hidden") - mock_details_table.clear.assert_called_once() - mock_details_table.add_column.assert_called_with("Field", key="field") - mock_details_table.add_row.assert_called_with("No entries loaded") + assert mock_ip_input.value == "" + assert mock_ip_input.placeholder == "No entries loaded" + assert mock_hostname_input.value == "" + assert mock_hostname_input.placeholder == "No entries loaded" + assert mock_comment_input.value == "" + assert mock_comment_input.placeholder == "No entries loaded" + assert not mock_active_checkbox.value def test_update_status_default(self): """Test status bar update with default information."""