Implement search functionality with input field and filtering in the hosts manager
This commit is contained in:
parent
07e7e4f70f
commit
5b768c004b
5 changed files with 140 additions and 52 deletions
|
@ -18,7 +18,6 @@ from .config_modal import ConfigModal
|
|||
from .password_modal import PasswordModal
|
||||
from .add_entry_modal import AddEntryModal
|
||||
from .delete_confirmation_modal import DeleteConfirmationModal
|
||||
from .search_modal import SearchModal
|
||||
from .help_modal import HelpModal
|
||||
from .styles import HOSTS_MANAGER_CSS
|
||||
from .keybindings import HOSTS_MANAGER_BINDINGS
|
||||
|
@ -46,6 +45,7 @@ class HostsManagerApp(App):
|
|||
entry_edit_mode: reactive[bool] = reactive(False)
|
||||
sort_column: reactive[str] = reactive("") # "ip" or "hostname"
|
||||
sort_ascending: reactive[bool] = reactive(True)
|
||||
search_term: reactive[str] = reactive("")
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
@ -71,6 +71,18 @@ class HostsManagerApp(App):
|
|||
yield Header()
|
||||
yield Footer()
|
||||
|
||||
# Spacer
|
||||
yield Static("", classes="spacer")
|
||||
|
||||
# Search bar above the panes
|
||||
with Horizontal(classes="search-container") as search_container:
|
||||
search_container.border_title = "Search"
|
||||
yield Input(
|
||||
placeholder="Filter by hostname, IP address, or comment...",
|
||||
id="search-input",
|
||||
classes="search-input",
|
||||
)
|
||||
|
||||
with Horizontal(classes="hosts-container"):
|
||||
# Left pane - entries table
|
||||
with Vertical(classes="left-pane") as left_pane:
|
||||
|
@ -190,15 +202,38 @@ class HostsManagerApp(App):
|
|||
|
||||
def on_key(self, event) -> None:
|
||||
"""Handle key events to override default tab behavior in edit mode."""
|
||||
# Handle tab navigation for search bar and data table
|
||||
if event.key == "tab" and not self.entry_edit_mode:
|
||||
search_input = self.query_one("#search-input", Input)
|
||||
entries_table = self.query_one("#entries-table", DataTable)
|
||||
|
||||
# Check which widget currently has focus
|
||||
if self.focused == search_input:
|
||||
# Focus on entries table
|
||||
entries_table.focus()
|
||||
event.prevent_default()
|
||||
return
|
||||
elif self.focused == entries_table:
|
||||
# Focus on search input
|
||||
search_input.focus()
|
||||
event.prevent_default()
|
||||
return
|
||||
|
||||
# Delegate to edit handler for edit mode navigation
|
||||
if self.edit_handler.handle_entry_edit_key_event(event):
|
||||
return # Event was handled by edit handler
|
||||
|
||||
def on_input_changed(self, event: Input.Changed) -> None:
|
||||
"""Handle input field changes (no auto-save - changes saved on exit)."""
|
||||
# Input changes are tracked but not automatically saved
|
||||
# Changes will be validated and saved when exiting edit mode
|
||||
pass
|
||||
if event.input.id == "search-input":
|
||||
# Update search term and filter entries
|
||||
self.search_term = event.value.strip()
|
||||
self.table_handler.populate_entries_table()
|
||||
self.details_handler.update_entry_details()
|
||||
else:
|
||||
# Edit form input changes are tracked but not automatically saved
|
||||
# Changes will be validated and saved when exiting edit mode
|
||||
pass
|
||||
|
||||
def on_checkbox_changed(self, event: Checkbox.Changed) -> None:
|
||||
"""Handle checkbox changes (no auto-save - changes saved on exit)."""
|
||||
|
@ -429,38 +464,10 @@ class HostsManagerApp(App):
|
|||
self.push_screen(DeleteConfirmationModal(entry), handle_delete_confirmation)
|
||||
|
||||
def action_search(self) -> None:
|
||||
"""Show the search modal."""
|
||||
if not self.hosts_file.entries:
|
||||
self.update_status("No entries to search")
|
||||
return
|
||||
|
||||
def handle_search_result(selected_index) -> None:
|
||||
if selected_index is None:
|
||||
self.update_status("Search cancelled")
|
||||
return
|
||||
|
||||
if 0 <= selected_index < len(self.hosts_file.entries):
|
||||
# Update selected entry and refresh display
|
||||
self.selected_entry_index = selected_index
|
||||
self.table_handler.populate_entries_table()
|
||||
|
||||
# Move cursor to the found entry
|
||||
display_index = self.table_handler.actual_index_to_display_index(
|
||||
selected_index
|
||||
)
|
||||
table = self.query_one("#entries-table", DataTable)
|
||||
if display_index < table.row_count:
|
||||
table.move_cursor(row=display_index)
|
||||
|
||||
self.details_handler.update_entry_details()
|
||||
entry = self.hosts_file.entries[selected_index]
|
||||
self.update_status(
|
||||
f"Found: {entry.canonical_hostname} ({entry.ip_address})"
|
||||
)
|
||||
else:
|
||||
self.update_status("Selected entry not found")
|
||||
|
||||
self.push_screen(SearchModal(self.hosts_file.entries), handle_search_result)
|
||||
"""Focus the search bar for filtering entries."""
|
||||
search_input = self.query_one("#search-input", Input)
|
||||
search_input.focus()
|
||||
self.update_status("Use the search bar to filter entries")
|
||||
|
||||
def action_quit(self) -> None:
|
||||
"""Quit the application."""
|
||||
|
|
|
@ -93,33 +93,70 @@ class HelpModal(ModalScreen):
|
|||
# Main Commands section
|
||||
with Vertical(classes="help-section"):
|
||||
yield Static("Main Commands", classes="help-section-title")
|
||||
yield Static("[bold]r[/bold] Reload [bold]h[/bold] Help [bold]c[/bold] Config [bold]Ctrl+F[/bold] Search [bold]q[/bold] Quit", classes="help-item")
|
||||
yield Static("[bold]i[/bold] Sort by IP [bold]n[/bold] Sort by hostname", classes="help-item")
|
||||
yield Static(
|
||||
"[bold]r[/bold] Reload [bold]h[/bold] Help [bold]c[/bold] Config [bold]Ctrl+F[/bold] Search [bold]q[/bold] Quit",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"[bold]i[/bold] Sort by IP [bold]n[/bold] Sort by hostname",
|
||||
classes="help-item",
|
||||
)
|
||||
|
||||
# Edit Mode section
|
||||
with Vertical(classes="help-section"):
|
||||
yield Static("Edit Mode Commands", classes="help-section-title")
|
||||
yield Static("[bold]Ctrl+E[/bold] - Toggle edit mode (requires sudo)", classes="help-item")
|
||||
yield Static("[italic]Edit mode commands:[/italic] [bold]Space[/bold] Toggle [bold]a[/bold] Add [bold]d[/bold] Delete [bold]e[/bold] Edit", classes="help-item")
|
||||
yield Static("[bold]Shift+↑/↓[/bold] Move entry [bold]Ctrl+S[/bold] Save file", classes="help-item")
|
||||
yield Static(
|
||||
"[bold]Ctrl+E[/bold] - Toggle edit mode (requires sudo)",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"[italic]Edit mode commands:[/italic] [bold]Space[/bold] Toggle [bold]a[/bold] Add [bold]d[/bold] Delete [bold]e[/bold] Edit",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"[bold]Shift+↑/↓[/bold] Move entry [bold]Ctrl+S[/bold] Save file",
|
||||
classes="help-item",
|
||||
)
|
||||
|
||||
# Form Navigation section
|
||||
with Vertical(classes="help-section"):
|
||||
yield Static("Form & Modal Navigation", classes="help-section-title")
|
||||
yield Static("[bold]Tab/Shift+Tab[/bold] Navigate fields [bold]Enter[/bold] Confirm/Save [bold]Escape[/bold] Cancel/Exit", classes="help-item")
|
||||
yield Static(
|
||||
"Form & Modal Navigation", classes="help-section-title"
|
||||
)
|
||||
yield Static(
|
||||
"[bold]Tab/Shift+Tab[/bold] Navigate fields [bold]Enter[/bold] Confirm/Save [bold]Escape[/bold] Cancel/Exit",
|
||||
classes="help-item",
|
||||
)
|
||||
|
||||
# Special Commands section
|
||||
with Vertical(classes="help-section"):
|
||||
yield Static("Special Dialog Commands", classes="help-section-title")
|
||||
yield Static("[bold]F3[/bold] Search in search dialog [bold]s[/bold] Save changes [bold]d[/bold] Discard changes", classes="help-item")
|
||||
yield Static(
|
||||
"Special Dialog Commands", classes="help-section-title"
|
||||
)
|
||||
yield Static(
|
||||
"[bold]F3[/bold] Search in search dialog [bold]s[/bold] Save changes [bold]d[/bold] Discard changes",
|
||||
classes="help-item",
|
||||
)
|
||||
|
||||
# Status and Tips section
|
||||
with Vertical(classes="help-section"):
|
||||
yield Static("Entry Status & Tips", classes="help-section-title")
|
||||
yield Static("✓ Active (enabled) ✗ Inactive (commented out)", classes="help-item")
|
||||
yield Static("• Edit mode commands require [bold]Ctrl+E[/bold] first", classes="help-item")
|
||||
yield Static("• Search supports partial matches in IP, hostname, or comment", classes="help-item")
|
||||
yield Static("• Edit mode creates automatic backups • System entries cannot be modified", classes="help-item")
|
||||
yield Static(
|
||||
"✓ Active (enabled) ✗ Inactive (commented out)",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"• Edit mode commands require [bold]Ctrl+E[/bold] first",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"• Search supports partial matches in IP, hostname, or comment",
|
||||
classes="help-item",
|
||||
)
|
||||
yield Static(
|
||||
"• Edit mode creates automatic backups • System entries cannot be modified",
|
||||
classes="help-item",
|
||||
)
|
||||
|
||||
with Horizontal(classes="button-row"):
|
||||
yield Button(
|
||||
|
|
|
@ -15,7 +15,7 @@ HOSTS_MANAGER_BINDINGS = [
|
|||
Binding("i", "sort_by_ip", "Sort by IP"),
|
||||
Binding("n", "sort_by_hostname", "Sort by Hostname"),
|
||||
Binding("c", "config", "Config"),
|
||||
Binding("ctrl+f", "search", "Search"),
|
||||
Binding("ctrl+f", "search", "Focus Search"),
|
||||
Binding("ctrl+e", "toggle_edit_mode", "Edit Mode"),
|
||||
Binding("a", "add_entry", "Add Entry", show=False),
|
||||
Binding("d", "delete_entry", "Delete Entry", show=False),
|
||||
|
|
|
@ -7,6 +7,19 @@ across the application.
|
|||
|
||||
# CSS styles for the hosts manager application
|
||||
HOSTS_MANAGER_CSS = """
|
||||
.search-container {
|
||||
border: round $primary;
|
||||
height: 3;
|
||||
padding: 0 1;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 1fr;
|
||||
height: 1;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hosts-container {
|
||||
height: 1fr;
|
||||
}
|
||||
|
@ -105,6 +118,11 @@ HOSTS_MANAGER_CSS = """
|
|||
background: $surface;
|
||||
}
|
||||
|
||||
Header { height: 1; }
|
||||
Header.-tall { height: 1; } /* Fix tall header also to height 1 */
|
||||
Header {
|
||||
height: 1;
|
||||
}
|
||||
|
||||
Header.-tall {
|
||||
height: 1; /* Fix tall header also to height 1 */
|
||||
}
|
||||
"""
|
||||
|
|
|
@ -28,6 +28,32 @@ class TableHandler:
|
|||
entry.ip_address, canonical_hostname
|
||||
):
|
||||
continue
|
||||
|
||||
# Apply search filter if search term is provided
|
||||
if self.app.search_term:
|
||||
search_term_lower = self.app.search_term.lower()
|
||||
matches_search = False
|
||||
|
||||
# Search in IP address
|
||||
if search_term_lower in entry.ip_address.lower():
|
||||
matches_search = True
|
||||
|
||||
# Search in hostnames
|
||||
if not matches_search:
|
||||
for hostname in entry.hostnames:
|
||||
if search_term_lower in hostname.lower():
|
||||
matches_search = True
|
||||
break
|
||||
|
||||
# Search in comment
|
||||
if not matches_search and entry.comment:
|
||||
if search_term_lower in entry.comment.lower():
|
||||
matches_search = True
|
||||
|
||||
# Skip entry if it doesn't match search term
|
||||
if not matches_search:
|
||||
continue
|
||||
|
||||
visible_entries.append(entry)
|
||||
|
||||
return visible_entries
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue