From a853d36a6f36a7ca3cd094cc0e6966cc302a03e0 Mon Sep 17 00:00:00 2001 From: Muhammad Ibrahim Date: Sat, 22 Nov 2025 14:34:53 +0000 Subject: [PATCH] Mobile fixes #1 --- frontend/src/components/GlobalSearch.jsx | 55 ++-- frontend/src/components/Layout.jsx | 266 ++++++++++++++++-- frontend/src/pages/Dashboard.jsx | 115 ++++---- frontend/src/pages/Hosts.jsx | 344 +++++++++++++++++++---- 4 files changed, 622 insertions(+), 158 deletions(-) diff --git a/frontend/src/components/GlobalSearch.jsx b/frontend/src/components/GlobalSearch.jsx index d1f5c91..4166e84 100644 --- a/frontend/src/components/GlobalSearch.jsx +++ b/frontend/src/components/GlobalSearch.jsx @@ -215,7 +215,7 @@ const GlobalSearch = () => { { @@ -237,9 +238,9 @@ const GlobalSearch = () => { {/* Dropdown Results */} {isOpen && ( -
+
{isLoading ? ( -
+
Searching...
) : hasResults ? ( @@ -247,7 +248,7 @@ const GlobalSearch = () => { {/* Hosts */} {results.hosts?.length > 0 && (
-
+
Hosts
{results.hosts.map((host, _idx) => { @@ -260,7 +261,7 @@ const GlobalSearch = () => { type="button" key={host.id} onClick={() => handleResultClick(host)} - className={`flex w-full items-center gap-2 px-3 py-1.5 text-left transition-colors ${ + className={`flex w-full items-center gap-2 px-3 py-3 sm:py-1.5 text-left transition-colors min-h-[44px] ${ globalIdx === selectedIndex ? "bg-primary-50 dark:bg-primary-900/20" : "hover:bg-secondary-50 dark:hover:bg-secondary-700" @@ -271,12 +272,14 @@ const GlobalSearch = () => { {display.primary} - - + + • + + {display.secondary}
-
+
{host.os_type}
@@ -288,7 +291,7 @@ const GlobalSearch = () => { {/* Packages */} {results.packages?.length > 0 && (
-
+
Packages
{results.packages.map((pkg, _idx) => { @@ -301,7 +304,7 @@ const GlobalSearch = () => { type="button" key={pkg.id} onClick={() => handleResultClick(pkg)} - className={`flex w-full items-center gap-2 px-3 py-1.5 text-left transition-colors ${ + className={`flex w-full items-center gap-2 px-3 py-3 sm:py-1.5 text-left transition-colors min-h-[44px] ${ globalIdx === selectedIndex ? "bg-primary-50 dark:bg-primary-900/20" : "hover:bg-secondary-50 dark:hover:bg-secondary-700" @@ -317,13 +320,13 @@ const GlobalSearch = () => { - + {display.secondary} )}
-
+
{pkg.host_count} hosts
@@ -335,7 +338,7 @@ const GlobalSearch = () => { {/* Repositories */} {results.repositories?.length > 0 && (
-
+
Repositories
{results.repositories.map((repo, _idx) => { @@ -348,7 +351,7 @@ const GlobalSearch = () => { type="button" key={repo.id} onClick={() => handleResultClick(repo)} - className={`flex w-full items-center gap-2 px-3 py-1.5 text-left transition-colors ${ + className={`flex w-full items-center gap-2 px-3 py-3 sm:py-1.5 text-left transition-colors min-h-[44px] ${ globalIdx === selectedIndex ? "bg-primary-50 dark:bg-primary-900/20" : "hover:bg-secondary-50 dark:hover:bg-secondary-700" @@ -359,12 +362,14 @@ const GlobalSearch = () => { {display.primary} - - + + • + + {display.secondary}
-
+
{repo.host_count} hosts
@@ -376,7 +381,7 @@ const GlobalSearch = () => { {/* Users */} {results.users?.length > 0 && (
-
+
Users
{results.users.map((user, _idx) => { @@ -389,7 +394,7 @@ const GlobalSearch = () => { type="button" key={user.id} onClick={() => handleResultClick(user)} - className={`flex w-full items-center gap-2 px-3 py-1.5 text-left transition-colors ${ + className={`flex w-full items-center gap-2 px-3 py-3 sm:py-1.5 text-left transition-colors min-h-[44px] ${ globalIdx === selectedIndex ? "bg-primary-50 dark:bg-primary-900/20" : "hover:bg-secondary-50 dark:hover:bg-secondary-700" @@ -400,12 +405,14 @@ const GlobalSearch = () => { {display.primary} - - + + • + + {display.secondary}
-
+
{user.role}
@@ -415,7 +422,7 @@ const GlobalSearch = () => { )}
) : query.trim() ? ( -
+
No results found for "{query}"
) : null} diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 1d72ca0..152d5d5 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -46,6 +46,7 @@ const Layout = ({ children }) => { }); const [_userMenuOpen, setUserMenuOpen] = useState(false); const [githubStars, setGithubStars] = useState(null); + const [mobileLinksOpen, setMobileLinksOpen] = useState(false); const location = useLocation(); const navigate = useNavigate(); const { @@ -531,8 +532,9 @@ const Layout = ({ children }) => {
@@ -546,7 +548,7 @@ const Layout = ({ children }) => { {/* Show message for users with very limited permissions */} {navigation.length === 0 && settingsNavigation.length === 0 && (
-
+

Limited access

Contact your administrator for additional permissions @@ -561,7 +563,7 @@ const Layout = ({ children }) => { { // Special handling for Hosts item with integrated + button (mobile) { handleLogout(); setSidebarOpen(false); }} - className="w-full group flex items-center px-2 py-2 text-sm font-medium rounded-md text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white" + className="w-full group flex items-center px-2 py-3 text-sm font-medium rounded-md text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white min-h-[44px]" > Sign out @@ -805,7 +807,7 @@ const Layout = ({ children }) => { {/* Show message for users with very limited permissions */} {navigation.length === 0 && settingsNavigation.length === 0 && (

  • -
    +

    Limited access

    Contact your administrator for additional permissions @@ -1080,7 +1082,7 @@ const Layout = ({ children }) => { {/* Updated info */} {stats && (

    -
    +
    Updated: {formatRelativeTimeShort(stats.lastUpdated)} @@ -1097,7 +1099,7 @@ const Layout = ({ children }) => { /> {versionInfo && ( - + v{versionInfo.version} )} @@ -1141,7 +1143,7 @@ const Layout = ({ children }) => { /> {versionInfo && ( - + v{versionInfo.version} )} @@ -1161,7 +1163,7 @@ const Layout = ({ children }) => { > {/* Top bar */}
    { > @@ -1179,7 +1182,7 @@ const Layout = ({ children }) => { {/* Separator */}
    -
    +
    {/* Page title - hidden on dashboard, hosts, repositories, packages, automation, docker, and host details to give more space to search */} {![ "/", @@ -1191,8 +1194,8 @@ const Layout = ({ children }) => { ].includes(location.pathname) && !location.pathname.startsWith("/hosts/") && !location.pathname.startsWith("/docker/") && ( -
    -

    +
    +

    {getPageTitle()}

    @@ -1200,20 +1203,191 @@ const Layout = ({ children }) => { {/* Global Search Bar */}
    -
    - {/* External Links */} +
    + {/* Mobile External Links Menu */} +
    + + {mobileLinksOpen && ( + <> +
    + + {/* Desktop External Links */}
    {/* 1) GitHub */} @@ -1230,7 +1404,12 @@ const Layout = ({ children }) => { href="https://buymeacoffee.com/iby___" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Buy Me a Coffee" aria-label="Buy Me a Coffee" > @@ -1248,7 +1427,12 @@ const Layout = ({ children }) => { href="https://github.com/orgs/PatchMon/projects/2/views/1" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Roadmap" > @@ -1258,7 +1442,12 @@ const Layout = ({ children }) => { href="https://docs.patchmon.net" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Documentation" > @@ -1268,7 +1457,12 @@ const Layout = ({ children }) => { href="https://patchmon.net/discord" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Discord" > @@ -1276,7 +1470,12 @@ const Layout = ({ children }) => { {/* 6) Email */} @@ -1286,7 +1485,12 @@ const Layout = ({ children }) => { href="https://youtube.com/@patchmonTV" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="YouTube Channel" > @@ -1296,7 +1500,12 @@ const Layout = ({ children }) => { href="https://www.reddit.com/r/patchmon" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Reddit Community" > @@ -1306,7 +1515,12 @@ const Layout = ({ children }) => { href="https://patchmon.net" target="_blank" rel="noopener noreferrer" - className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-gray-800 text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors shadow-sm" + className="flex items-center justify-center w-10 h-10 bg-gray-50 dark:bg-transparent text-secondary-600 dark:text-secondary-300 hover:bg-gray-100 dark:hover:bg-white/10 rounded-lg transition-colors shadow-sm" + style={{ + backgroundColor: "var(--button-bg, rgb(249, 250, 251))", + backdropFilter: "var(--button-blur, none)", + WebkitBackdropFilter: "var(--button-blur, none)", + }} title="Visit patchmon.net" > diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index d18c83c..eb5a703 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -22,7 +22,6 @@ import { Server, Settings, Shield, - TrendingUp, Users, WifiOff, } from "lucide-react"; @@ -59,6 +58,7 @@ const Dashboard = () => { const [packageTrendsHost, setPackageTrendsHost] = useState("all"); // host filter const [systemStatsJobId, setSystemStatsJobId] = useState(null); // Track job ID for system statistics const [isTriggeringJob, setIsTriggeringJob] = useState(false); + const [isMobile, setIsMobile] = useState(window.innerWidth < 640); const navigate = useNavigate(); const { isDark } = useTheme(); const { user } = useAuth(); @@ -312,6 +312,15 @@ const Dashboard = () => { }; }, []); + // Track window size for responsive chart options + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 640); + }; + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + // Helper function to check if a card should be displayed const isCardEnabled = (cardId) => { const card = cardPreferences.find((c) => c.cardId === cardId); @@ -358,11 +367,11 @@ const Dashboard = () => { const getGroupClassName = (cardType) => { switch (cardType) { case "stats": - return "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4"; + return "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3 sm:gap-4"; case "charts": - return "grid grid-cols-1 lg:grid-cols-3 gap-6"; + return "grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6"; case "widecharts": - return "grid grid-cols-1 lg:grid-cols-3 gap-6"; + return "grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6"; case "fullwidth": return "space-y-6"; default: @@ -377,7 +386,7 @@ const Dashboard = () => { return (
    {/* Advanced Filters */} {showFilters && ( -
    -
    +
    +
    -
    +
    {!hosts || hosts.length === 0 ? (
    @@ -1644,7 +1652,7 @@ const Hosts = () => {

    ) : ( -
    +
    {Object.entries(groupedHosts).map( ([groupName, groupHosts]) => ( @@ -1658,15 +1666,234 @@ const Hosts = () => {
    )} - {/* Table for this group */} -
    - + {/* Mobile Card Layout */} +
    + {groupHosts.map((host) => { + const isInactive = + (host.effectiveStatus || host.status) === + "inactive"; + const isSelected = selectedHosts.includes(host.id); + const wsStatus = wsStatusMap[host.api_id]; + const groupIds = + host.host_group_memberships?.map( + (membership) => membership.host_groups.id, + ) || []; + const groups = + hostGroups?.filter((g) => + groupIds.includes(g.id), + ) || []; + + return ( +
    + {/* Header with select and main info */} +
    +
    + {visibleColumns.some( + (col) => col.id === "select", + ) && ( + + )} +
    + {visibleColumns.some( + (col) => col.id === "host", + ) && ( + + {host.friendly_name || "Unnamed Host"} + + )} + {visibleColumns.some( + (col) => col.id === "hostname", + ) && + host.hostname && ( +
    + {host.hostname} +
    + )} +
    +
    + {visibleColumns.some( + (col) => col.id === "actions", + ) && ( + + View + + + )} +
    + + {/* Status and connection info */} +
    + {visibleColumns.some( + (col) => col.id === "status", + ) && ( + + {(host.effectiveStatus || host.status) + .charAt(0) + .toUpperCase() + + ( + host.effectiveStatus || host.status + ).slice(1)} + + )} + {visibleColumns.some( + (col) => col.id === "ws_status", + ) && + wsStatus && ( + +
    + + {wsStatus.connected + ? wsStatus.secure + ? "WSS" + : "WS" + : "Offline"} + +
    + )} + {visibleColumns.some( + (col) => col.id === "needs_reboot", + ) && + (host.needs_reboot ? ( + + + Reboot Required + + ) : null)} +
    + + {/* OS and Group info */} +
    + {visibleColumns.some( + (col) => col.id === "os", + ) && ( +
    + + + {getOSDisplayName(host.os_type)} + +
    + )} + {visibleColumns.some( + (col) => col.id === "group", + ) && + groups.length > 0 && ( +
    + + Groups: + + {groups.map((g, idx) => ( + + {g.name} + {idx < groups.length - 1 ? "," : ""} + + ))} +
    + )} +
    + + {/* Updates info */} +
    + {visibleColumns.some( + (col) => col.id === "updates", + ) && ( + + )} + {visibleColumns.some( + (col) => col.id === "security_updates", + ) && ( + + )} + {visibleColumns.some( + (col) => col.id === "last_update", + ) && ( +
    + Updated{" "} + {formatRelativeTime(host.last_update)} +
    + )} +
    +
    + ); + })} +
    + + {/* Desktop Table Layout */} +
    +
    {visibleColumns.map((column) => ( @@ -2184,7 +2411,6 @@ const ColumnSettingsModal = ({ key={column.id} type="button" draggable - tabIndex={0} aria-label={`Drag to reorder ${column.label} column`} onDragStart={(e) => handleDragStart(e, index)} onDragOver={handleDragOver} @@ -2195,7 +2421,7 @@ const ColumnSettingsModal = ({ // Focus handling for keyboard users } }} - className={`flex items-center justify-between p-2.5 border rounded-lg cursor-move w-full text-left transition-colors ${ + className={`flex items-center justify-between p-2.5 border rounded-lg cursor-move w-full transition-colors ${ draggedIndex === index ? "opacity-50" : "hover:bg-secondary-50 dark:hover:bg-secondary-700" @@ -2209,12 +2435,20 @@ const ColumnSettingsModal = ({
    {column.id === "select" ? ( {renderCellContent(column, host)}