(function() { 'use strict'; var KEY = 'unraid-sidebar-expanded'; // Restore state immediately (before DOM ready to avoid flash) if (localStorage.getItem(KEY) === '1') { document.documentElement.classList.add('sidebar-expanded'); } function init() { var menu = document.getElementById('menu'); if (!menu) return; // --- Unraid logo (absolute positioned at very top of sidebar) --- if (!document.getElementById('sidebar-logo')) { var logo = document.createElement('img'); logo.id = 'sidebar-logo'; logo.src = '/custom/assets/icons/unraid.svg'; logo.alt = ''; menu.appendChild(logo); } // --- Toggle button with chevron (below logo) --- if (!document.getElementById('sidebar-toggle-btn')) { var btn = document.createElement('button'); btn.id = 'sidebar-toggle-btn'; btn.type = 'button'; btn.title = 'Toggle Sidebar'; btn.innerHTML = ''; btn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); var expanded = document.documentElement.classList.toggle('sidebar-expanded'); localStorage.setItem(KEY, expanded ? '1' : '0'); }); menu.appendChild(btn); } // --- Search overlay (delayed to wait for search plugin init) --- setTimeout(initSearchOverlay, 800); } function initSearchOverlay() { if (!window.jQuery) return; var searchItem = document.querySelector('.nav-item.gui_search'); if (!searchItem) return; // Remove default hover-based open/close handlers $(searchItem).off('mouseenter mouseleave'); var link = searchItem.querySelector('a'); if (!link) return; // Remove any inline onclick handler (Unraid sets onclick="gui_search()") link.removeAttribute('onclick'); link.onclick = null; // Replace hover with click link.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); toggleSearch(); }); // Add Cmd+K / Ctrl+K keyboard shortcut (sidebar theme doesn't have one by default) document.addEventListener('keydown', function(e) { var isMac = navigator.platform.indexOf('Mac') > -1; if ((isMac ? e.metaKey : e.ctrlKey) && (e.key === 'k' || e.keyCode === 75)) { if ($('[role="modal"]:visible').length) return; e.preventDefault(); toggleSearch(); } }); } function toggleSearch() { var existing = document.getElementById('guiSearchBoxSpan'); if (existing) { closeSearch(); return; } // Call original gui_search to create the input if (typeof gui_search === 'function') { gui_search(); } // Move search box to
— backdrop-filter on #menu creates a new // containing block, so position:fixed inside it is relative to #menu // and gets clipped by overflow:hidden. Moving to body fixes this. var searchSpan = document.getElementById('guiSearchBoxSpan'); if (searchSpan) { document.body.appendChild(searchSpan); } // Create backdrop overlay if (!document.getElementById('search-backdrop')) { var backdrop = document.createElement('div'); backdrop.id = 'search-backdrop'; backdrop.addEventListener('click', function() { closeSearch(); }); document.body.appendChild(backdrop); } // Override blur/keydown behavior on the input var input = document.getElementById('guiSearchBox'); if (input && window.jQuery) { $(input).off('blur keydown'); input.addEventListener('blur', function() { setTimeout(function() { var span = document.getElementById('guiSearchBoxSpan'); if (span && !span.contains(document.activeElement)) { closeSearch(); } }, 300); }); input.addEventListener('keydown', function(e) { if (e.key === 'Escape' || e.keyCode === 27) { e.preventDefault(); closeSearch(); } }); input.focus(); } } function closeSearch() { // Remove the search span directly (works whether moved to body or not) var span = document.getElementById('guiSearchBoxSpan'); if (span) span.remove(); var backdrop = document.getElementById('search-backdrop'); if (backdrop) backdrop.remove(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(); // ============================================================= // Icon Replacement Module — selfh.st CDN icons for Docker/VMs // ============================================================= (function() { 'use strict'; var CDN_PNG = '/custom/assets/icons'; var CDN_SVG = '/custom/assets/icons'; // Container name → selfh.st icon name (lowercase) // Only needed for names that don't auto-match. // Add entries here or use the glass.icon Docker label. var ICON_MAP = { 'autokuma': 'uptime-kuma', 'uptimekuma': 'uptime-kuma', 'uptime-kuma-api': 'uptime-kuma', 'homeassistant_inabox': 'home-assistant', 'dockersocket': 'docker', 'pgadmin4': 'pgadmin', 'postgresql17': 'postgresql', 'woodpecker-agent': 'woodpecker-ci', 'woodpecker-server': 'woodpecker-ci', 'rustdesk-hbbr': 'rustdesk', 'rustdesk-hbbs': 'rustdesk', 'rustfs': 'rustdesk', 'netbox-redis-cache': 'redis', 'netbox-worker': 'netbox', 'netdisco-backend': 'networking-toolbox', 'netdisco-web': 'networking-toolbox', 'diode-agent': 'netbox', 'diode-auth': 'netbox', 'diode-auth-bootstrap': 'netbox', 'diode-hydra': 'netbox', 'diode-hydra-migrate': 'netbox', 'diode-ingester': 'netbox', 'diode-ingress': 'netbox', 'diode-reconciler': 'netbox', 'authentik-worker': 'authentik', 'adguardhome': 'adguard-home', 'adguardhome-sync': 'adguard-home', 'timemachine': 'apple', 'libation': 'audible', 'netalertx': 'netalertx', 'actual-budget': 'actual-budget', 'speedtest-tracker': 'speedtest-tracker', 'open-webui': 'open-webui', 'urbackup': 'networking-toolbox', 'seekandwatch': 'databasement', 'unmarr': 'homarr', 'dockhand': 'docker' }; // Resolve container name to selfh.st CDN URL // mode: 'color' for expanded containers, 'light' for dashboard/folder-preview function resolveIconUrl(name, mode) { var key = name.toLowerCase(); var iconName = ICON_MAP[key] || key; if (mode === 'light') { return CDN_SVG + '/' + iconName + '-light.svg'; } return CDN_PNG + '/' + iconName + '.png'; } // Extract container name from an