From 0bfaaba9ee2373832e1922633884ac414065897b Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 24 Oct 2025 10:06:45 +0000 Subject: [PATCH] feat: improve Docker and Hosts page UX - Hide Docker host sidebar when only 1 host is configured to save horizontal space - Add keyboard auto-focus for search fields - typing anywhere on the page automatically focuses search - Add ESC key support to clear search and unfocus input - Fix vertical alignment of "+" icon in Docker Add Host button --- .../src/components/Docker/DockerFilter.tsx | 4 ++ .../src/components/Docker/DockerHosts.tsx | 40 +++++++++++++++++-- .../src/components/Hosts/HostsFilter.tsx | 4 ++ .../src/components/Hosts/HostsOverview.tsx | 34 +++++++++++++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/frontend-modern/src/components/Docker/DockerFilter.tsx b/frontend-modern/src/components/Docker/DockerFilter.tsx index c28f430..59122a2 100644 --- a/frontend-modern/src/components/Docker/DockerFilter.tsx +++ b/frontend-modern/src/components/Docker/DockerFilter.tsx @@ -118,6 +118,10 @@ export const DockerFilter: Component = (props) => { if (e.key === 'Enter') { commitSearchToHistory(e.currentTarget.value); closeHistory(); + } else if (e.key === 'Escape') { + props.setSearch(''); + closeHistory(); + e.currentTarget.blur(); } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { e.preventDefault(); setIsHistoryOpen(true); diff --git a/frontend-modern/src/components/Docker/DockerHosts.tsx b/frontend-modern/src/components/Docker/DockerHosts.tsx index d0c90db..3f90cbd 100644 --- a/frontend-modern/src/components/Docker/DockerHosts.tsx +++ b/frontend-modern/src/components/Docker/DockerHosts.tsx @@ -1,5 +1,5 @@ import type { Component } from 'solid-js'; -import { For, Show, createMemo, createSignal, createEffect, on } from 'solid-js'; +import { For, Show, createMemo, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import type { DockerHost, DockerContainer, Alert } from '@/types/api'; import { formatBytes, formatRelativeTime, formatUptime } from '@/utils/format'; @@ -406,6 +406,37 @@ export const DockerHosts: Component = (props) => { const [selectedHostId, setSelectedHostId] = createSignal(null); const [search, setSearch] = createSignal(''); + // Keyboard listener to auto-focus search + let searchInputRef: HTMLInputElement | undefined; + + const handleKeyDown = (e: KeyboardEvent) => { + // Don't interfere if user is already typing in an input + const target = e.target as HTMLElement; + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { + return; + } + + // Don't interfere with modifier key shortcuts (except Shift for capitals) + if (e.ctrlKey || e.metaKey || e.altKey) { + return; + } + + // Focus search on printable characters and start typing + if (e.key.length === 1 && searchInputRef) { + e.preventDefault(); + searchInputRef.focus(); + setSearch(search() + e.key); + } + }; + + onMount(() => { + document.addEventListener('keydown', handleKeyDown); + }); + + onCleanup(() => { + document.removeEventListener('keydown', handleKeyDown); + }); + // Unused - kept for potential future use // const hostSummaries = createMemo(() => { // return sortedHosts().map((host) => { @@ -624,11 +655,13 @@ export const DockerHosts: Component = (props) => { activeHostName={activeHostName()} onClearHost={() => setSelectedHostId(null)} onReset={() => setSelectedHostId(null)} + searchInputRef={(el) => (searchInputRef = el)} /> {/* Master-Detail Layout */}
- {/* Left: Host List */} + {/* Left: Host List - Only show if more than 1 host */} + 1}>
@@ -640,7 +673,7 @@ export const DockerHosts: Component = (props) => { onClick={() => navigate('/settings/docker')} class="inline-flex items-center gap-1 rounded border border-blue-200 dark:border-blue-500/40 bg-white dark:bg-blue-500/10 px-2 py-1 text-[11px] font-medium text-blue-600 dark:text-blue-300 shadow-sm hover:bg-blue-50 dark:hover:bg-blue-500/20 transition-colors" > - + + + Add Host
@@ -745,6 +778,7 @@ export const DockerHosts: Component = (props) => {
+
{/* Right: Container List */}
diff --git a/frontend-modern/src/components/Hosts/HostsFilter.tsx b/frontend-modern/src/components/Hosts/HostsFilter.tsx index 01dadf7..112918e 100644 --- a/frontend-modern/src/components/Hosts/HostsFilter.tsx +++ b/frontend-modern/src/components/Hosts/HostsFilter.tsx @@ -114,6 +114,10 @@ export const HostsFilter: Component = (props) => { if (e.key === 'Enter') { commitSearchToHistory(e.currentTarget.value); closeHistory(); + } else if (e.key === 'Escape') { + props.setSearch(''); + closeHistory(); + e.currentTarget.blur(); } else if (e.key === 'ArrowDown' && searchHistory().length > 0) { e.preventDefault(); setIsHistoryOpen(true); diff --git a/frontend-modern/src/components/Hosts/HostsOverview.tsx b/frontend-modern/src/components/Hosts/HostsOverview.tsx index 6559aa3..c5ae8d1 100644 --- a/frontend-modern/src/components/Hosts/HostsOverview.tsx +++ b/frontend-modern/src/components/Hosts/HostsOverview.tsx @@ -1,5 +1,5 @@ import type { Component } from 'solid-js'; -import { For, Show, createMemo, createSignal, createEffect, on } from 'solid-js'; +import { For, Show, createMemo, createSignal, createEffect, on, onMount, onCleanup } from 'solid-js'; import { useNavigate } from '@solidjs/router'; import type { Host } from '@/types/api'; import { formatBytes, formatRelativeTime, formatUptime } from '@/utils/format'; @@ -39,6 +39,37 @@ export const HostsOverview: Component = (props) => { const wsContext = useWebSocket(); const [search, setSearch] = createSignal(''); + // Keyboard listener to auto-focus search + let searchInputRef: HTMLInputElement | undefined; + + const handleKeyDown = (e: KeyboardEvent) => { + // Don't interfere if user is already typing in an input + const target = e.target as HTMLElement; + if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) { + return; + } + + // Don't interfere with modifier key shortcuts (except Shift for capitals) + if (e.ctrlKey || e.metaKey || e.altKey) { + return; + } + + // Focus search on printable characters and start typing + if (e.key.length === 1 && searchInputRef) { + e.preventDefault(); + searchInputRef.focus(); + setSearch(search() + e.key); + } + }; + + onMount(() => { + document.addEventListener('keydown', handleKeyDown); + }); + + onCleanup(() => { + document.removeEventListener('keydown', handleKeyDown); + }); + const connected = () => wsContext.connected(); const reconnecting = () => wsContext.reconnecting(); @@ -124,6 +155,7 @@ export const HostsOverview: Component = (props) => { search={search} setSearch={setSearch} onReset={() => setSearch('')} + searchInputRef={(el) => (searchInputRef = el)} /> {/* Host Table */}