diff --git a/frontend-modern/src/App.tsx b/frontend-modern/src/App.tsx index 15dde60a9..a34363005 100644 --- a/frontend-modern/src/App.tsx +++ b/frontend-modern/src/App.tsx @@ -1263,46 +1263,48 @@ function AppLayout(props: { return (
- {/* Header */} -
-
-
- - Pulse - - - RC - - + {/* Header - simplified in kiosk mode */} +
+ +
+
+ + Pulse + + + RC + + +
-
-
+ +
{/* AI Patrol Status Indicator */} @@ -1312,8 +1314,8 @@ function AppLayout(props: { type="button" onClick={toggleKioskMode} class={`group relative flex h-7 items-center justify-center gap-1 rounded-full px-2 text-xs transition-all duration-500 ease-in-out focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ${kioskMode() - ? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300 dark:hover:bg-blue-800' - : 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600' + ? 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-300 dark:hover:bg-blue-800' + : 'bg-gray-200 text-gray-700 hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600' }`} title={kioskMode() ? 'Exit kiosk mode (show navigation)' : 'Enter kiosk mode (hide navigation)'} aria-label={kioskMode() ? 'Exit kiosk mode' : 'Enter kiosk mode'} @@ -1370,56 +1372,18 @@ function AppLayout(props: {
- {/* Tabs */} -
-
- - {(platform) => { - const isActive = () => getActiveTab() === platform.id; - const disabled = () => !platform.enabled; - const baseClasses = - 'tab relative px-1.5 sm:px-3 py-1.5 text-xs sm:text-sm font-medium flex items-center gap-1 sm:gap-1.5 rounded-t border border-transparent transition-colors whitespace-nowrap cursor-pointer'; - - const className = () => { - if (isActive()) { - return `${baseClasses} bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 border-gray-300 dark:border-gray-700 border-b border-b-white dark:border-b-gray-800 shadow-sm font-semibold`; - } - if (disabled()) { - return `${baseClasses} cursor-not-allowed text-gray-400 dark:text-gray-600 opacity-70 bg-gray-100/40 dark:bg-gray-800/40`; - } - return `${baseClasses} text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-200/60 dark:hover:bg-gray-700/60`; - }; - - const title = () => - disabled() - ? `${platform.label} is not configured yet. Click to open settings.` - : platform.tooltip; - - return ( - - ); - }} - -
-
-
- - {(tab) => { - const isActive = () => getActiveTab() === tab.id; + {/* Tabs - hidden in kiosk mode */} + +
+
+ + {(platform) => { + const isActive = () => getActiveTab() === platform.id; + const disabled = () => !platform.enabled; const baseClasses = 'tab relative px-1.5 sm:px-3 py-1.5 text-xs sm:text-sm font-medium flex items-center gap-1 sm:gap-1.5 rounded-t border border-transparent transition-colors whitespace-nowrap cursor-pointer'; @@ -1427,55 +1391,95 @@ function AppLayout(props: { if (isActive()) { return `${baseClasses} bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 border-gray-300 dark:border-gray-700 border-b border-b-white dark:border-b-gray-800 shadow-sm font-semibold`; } + if (disabled()) { + return `${baseClasses} cursor-not-allowed text-gray-400 dark:text-gray-600 opacity-70 bg-gray-100/40 dark:bg-gray-800/40`; + } return `${baseClasses} text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-200/60 dark:hover:bg-gray-700/60`; }; + const title = () => + disabled() + ? `${platform.label} is not configured yet. Click to open settings.` + : platform.tooltip; + return ( ); }}
+
+
+ + {(tab) => { + const isActive = () => getActiveTab() === tab.id; + const baseClasses = + 'tab relative px-1.5 sm:px-3 py-1.5 text-xs sm:text-sm font-medium flex items-center gap-1 sm:gap-1.5 rounded-t border border-transparent transition-colors whitespace-nowrap cursor-pointer'; + + const className = () => { + if (isActive()) { + return `${baseClasses} bg-white dark:bg-gray-800 text-blue-600 dark:text-blue-400 border-gray-300 dark:border-gray-700 border-b border-b-white dark:border-b-gray-800 shadow-sm font-semibold`; + } + return `${baseClasses} text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 hover:bg-gray-200/60 dark:hover:bg-gray-700/60`; + }; + + return ( + + ); + }} + +
+
-
+ {/* Main Content */}
- {/* Footer */} - + {/* Footer - hidden in kiosk mode */} + + +
); } diff --git a/frontend-modern/src/components/Dashboard/Dashboard.tsx b/frontend-modern/src/components/Dashboard/Dashboard.tsx index ab2d13384..2ca11cece 100644 --- a/frontend-modern/src/components/Dashboard/Dashboard.tsx +++ b/frontend-modern/src/components/Dashboard/Dashboard.tsx @@ -24,7 +24,7 @@ import { useBreakpoint } from '@/hooks/useBreakpoint'; import { STORAGE_KEYS } from '@/utils/localStorage'; import { getBackupInfo } from '@/utils/format'; import { aiChatStore } from '@/stores/aiChat'; -import { isKioskMode } from '@/utils/url'; +import { isKioskMode, subscribeToKioskMode } from '@/utils/url'; type GuestMetadataRecord = Record; type IdleCallbackHandle = number; @@ -213,8 +213,17 @@ export function Dashboard(props: DashboardProps) { const alertsEnabled = createMemo(() => alertsActivation.activationState() === 'active'); // Kiosk mode - hide filter panel for clean dashboard display - // Usage: Add ?kiosk=1 to URL (persists across navigation via sessionStorage) - const kioskMode = createMemo(() => isKioskMode()); + // Usage: Add ?kiosk=1 to URL or use the toggle button in the header + const [kioskMode, setKioskMode] = createSignal(isKioskMode()); + + // Subscribe to kiosk mode changes from toggle button or URL params + onMount(() => { + const unsubscribe = subscribeToKioskMode((enabled) => { + setKioskMode(enabled); + }); + // Cleanup on unmount would go here, but Dashboard is always mounted + return unsubscribe; + }); const [search, setSearch] = createSignal(''); const [isSearchLocked, setIsSearchLocked] = createSignal(false); @@ -951,16 +960,18 @@ export function Dashboard(props: DashboardProps) { - {/* Unified Node Selector */} - g.type === 'qemu')} - filteredContainers={filteredGuests().filter((g) => g.type === 'lxc' || g.type === 'oci')} - searchTerm={search()} - /> + {/* Unified Node Selector - hidden in kiosk mode */} + + g.type === 'qemu')} + filteredContainers={filteredGuests().filter((g) => g.type === 'lxc' || g.type === 'oci')} + searchTerm={search()} + /> + {/* Dashboard Filter - hidden in kiosk mode */} diff --git a/frontend-modern/src/components/Docker/DockerHosts.tsx b/frontend-modern/src/components/Docker/DockerHosts.tsx index 2fbf8e0ef..f406a845c 100644 --- a/frontend-modern/src/components/Docker/DockerHosts.tsx +++ b/frontend-modern/src/components/Docker/DockerHosts.tsx @@ -21,7 +21,7 @@ import { STORAGE_KEYS } from '@/utils/localStorage'; import { DEGRADED_HEALTH_STATUSES, OFFLINE_HEALTH_STATUSES } from '@/utils/status'; import { MonitoringAPI } from '@/api/monitoring'; import { showSuccess, showError, showToast } from '@/utils/toast'; -import { isKioskMode } from '@/utils/url'; +import { isKioskMode, subscribeToKioskMode } from '@/utils/url'; type DockerMetadataRecord = Record; type DockerHostMetadataRecord = Record; @@ -98,7 +98,15 @@ export const DockerHosts: Component = (props) => { const hasSwarmClustersDetected = createMemo(() => hasSwarmClusters(sortedHosts())); // Kiosk mode - hide filter panel for clean dashboard display - const kioskMode = createMemo(() => isKioskMode()); + const [kioskMode, setKioskMode] = createSignal(isKioskMode()); + + // Subscribe to kiosk mode changes from toggle button + onMount(() => { + const unsubscribe = subscribeToKioskMode((enabled) => { + setKioskMode(enabled); + }); + return unsubscribe; + }); const clampPercent = (value: number | undefined | null) => { if (value === undefined || value === null || Number.isNaN(value)) return 0; diff --git a/frontend-modern/src/components/Hosts/HostsOverview.tsx b/frontend-modern/src/components/Hosts/HostsOverview.tsx index 14570bc18..6e11a0b0a 100644 --- a/frontend-modern/src/components/Hosts/HostsOverview.tsx +++ b/frontend-modern/src/components/Hosts/HostsOverview.tsx @@ -25,7 +25,7 @@ import { HostMetadataAPI, type HostMetadata } from '@/api/hostMetadata'; import { UrlEditPopover, createUrlEditState } from '@/components/shared/UrlEditPopover'; import { showSuccess, showError } from '@/utils/toast'; import { logger } from '@/utils/logger'; -import { isKioskMode } from '@/utils/url'; +import { isKioskMode, subscribeToKioskMode } from '@/utils/url'; // Column definition for hosts table export interface HostColumnDef { @@ -652,7 +652,15 @@ export const HostsOverview: Component = () => { const isColVisible = (colId: string) => visibleColumnIds().includes(colId); // Kiosk mode - hide filter panel for clean dashboard display - const kioskMode = createMemo(() => isKioskMode()); + const [kioskMode, setKioskMode] = createSignal(isKioskMode()); + + // Subscribe to kiosk mode changes from toggle button + onMount(() => { + const unsubscribe = subscribeToKioskMode((enabled) => { + setKioskMode(enabled); + }); + return unsubscribe; + }); // Host metadata management (for custom URLs) const [hostMetadata, setHostMetadata] = createSignal>({});