diff --git a/agents/patchmon-agent.sh b/agents/patchmon-agent.sh index 53b3c2a..8d16809 100755 --- a/agents/patchmon-agent.sh +++ b/agents/patchmon-agent.sh @@ -234,6 +234,10 @@ detect_os() { "rocky"|"almalinux") OS_TYPE="rhel" ;; + "ol") + # Keep Oracle Linux as 'ol' for proper frontend identification + OS_TYPE="ol" + ;; esac elif [[ -f /etc/redhat-release ]]; then @@ -261,7 +265,7 @@ get_repository_info() { "ubuntu"|"debian") get_apt_repositories repos_json first ;; - "centos"|"rhel"|"fedora") + "centos"|"rhel"|"fedora"|"ol") get_yum_repositories repos_json first ;; *) @@ -588,11 +592,11 @@ get_package_info() { "ubuntu"|"debian") get_apt_packages packages_json first ;; - "centos"|"rhel"|"fedora") + "centos"|"rhel"|"fedora"|"ol") get_yum_packages packages_json first ;; *) - error "Unsupported OS type: $OS_TYPE" + warning "Unsupported OS type: $OS_TYPE - returning empty package list" ;; esac diff --git a/frontend/src/components/Layout.jsx b/frontend/src/components/Layout.jsx index 3a08bfb..6ee89db 100644 --- a/frontend/src/components/Layout.jsx +++ b/frontend/src/components/Layout.jsx @@ -316,7 +316,7 @@ const Layout = ({ children }) => { onClick={() => setSidebarOpen(false)} aria-label="Close sidebar" /> -
+
- - ) : ( - // Standard navigation item (mobile) - e.preventDefault() - : () => setSidebarOpen(false) - } - > - - - {subItem.name} - {subItem.name === "Hosts" && - stats?.cards?.totalHosts !== undefined && ( - - {stats.cards.totalHosts} + + + {subItem.name} + {subItem.name === "Hosts" && + stats?.cards?.totalHosts !== undefined && ( + + {stats.cards.totalHosts} + + )} + + + + ) : ( + // Standard navigation item (mobile) + e.preventDefault() + : () => setSidebarOpen(false) + } + > + + + {subItem.name} + {subItem.name === "Hosts" && + stats?.cards?.totalHosts !== undefined && ( + + {stats.cards.totalHosts} + + )} + {subItem.comingSoon && ( + + Soon )} - {subItem.comingSoon && ( - - Soon - - )} - - - )} -
- ))} + + + )} +
+ ))}
); @@ -453,32 +455,70 @@ const Layout = ({ children }) => { return (
- {item.items.map((subItem) => ( - setSidebarOpen(false)} - > - - - {subItem.name} - {subItem.showUpgradeIcon && ( - - )} - - - ))} + {item.items + .filter((subItem) => !subItem.comingSoon) + .map((subItem) => ( + setSidebarOpen(false)} + > + + + {subItem.name} + {subItem.showUpgradeIcon && ( + + )} + + + ))}
); } return null; })} + + {/* Mobile Logout Section */} +
+
+ setSidebarOpen(false)} + > + + + {user?.first_name || user?.username} + {user?.role === "admin" && ( + + Admin + + )} + + + +
+
@@ -879,24 +919,29 @@ const Layout = ({ children }) => {
{/* Separator */} -
+
-
-

- {getPageTitle()} -

-
+ {/* Page title - hidden on dashboard to give more space to search */} + {location.pathname !== "/" && ( +
+

+ {getPageTitle()} +

+
+ )} {/* Global Search Bar */} -
+
diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 922d98a..fcfc20c 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -1150,23 +1150,46 @@ const Dashboard = () => { callbacks: { title: (context) => { const label = context[0].label; + + // Handle empty or invalid labels + if (!label || typeof label !== "string") { + return "Unknown Date"; + } + // Format hourly labels (e.g., "2025-10-07T14" -> "Oct 7, 2:00 PM") if (label.includes("T")) { - const date = new Date(`${label}:00:00`); + try { + const date = new Date(`${label}:00:00`); + // Check if date is valid + if (isNaN(date.getTime())) { + return label; // Return original label if date is invalid + } + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + hour12: true, + }); + } catch (error) { + return label; // Return original label if parsing fails + } + } + + // Format daily labels (e.g., "2025-10-07" -> "Oct 7") + try { + const date = new Date(label); + // Check if date is valid + if (isNaN(date.getTime())) { + return label; // Return original label if date is invalid + } return date.toLocaleDateString("en-US", { month: "short", day: "numeric", - hour: "numeric", - minute: "2-digit", - hour12: true, }); + } catch (error) { + return label; // Return original label if parsing fails } - // Format daily labels (e.g., "2025-10-07" -> "Oct 7") - const date = new Date(label); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }); }, }, }, @@ -1186,24 +1209,49 @@ const Dashboard = () => { }, callback: function (value, _index, _ticks) { const label = this.getLabelForValue(value); + + // Handle empty or invalid labels + if (!label || typeof label !== "string") { + return "Unknown"; + } + // Format hourly labels (e.g., "2025-10-07T14" -> "2 PM") if (label.includes("T")) { - const hour = label.split("T")[1]; - const hourNum = parseInt(hour, 10); - return hourNum === 0 - ? "12 AM" - : hourNum < 12 - ? `${hourNum} AM` - : hourNum === 12 - ? "12 PM" - : `${hourNum - 12} PM`; + try { + const hour = label.split("T")[1]; + const hourNum = parseInt(hour, 10); + + // Validate hour number + if (isNaN(hourNum) || hourNum < 0 || hourNum > 23) { + return hour; // Return original hour if invalid + } + + return hourNum === 0 + ? "12 AM" + : hourNum < 12 + ? `${hourNum} AM` + : hourNum === 12 + ? "12 PM" + : `${hourNum - 12} PM`; + } catch (error) { + return label; // Return original label if parsing fails + } } + // Format daily labels (e.g., "2025-10-07" -> "Oct 7") - const date = new Date(label); - return date.toLocaleDateString("en-US", { - month: "short", - day: "numeric", - }); + try { + const date = new Date(label); + // Check if date is valid + if (isNaN(date.getTime())) { + return label; // Return original label if date is invalid + } + return date.toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); + } catch (error) { + return label; // Return original label if parsing fails + } }, }, grid: { diff --git a/frontend/src/pages/Hosts.jsx b/frontend/src/pages/Hosts.jsx index 462c543..7c5f34d 100644 --- a/frontend/src/pages/Hosts.jsx +++ b/frontend/src/pages/Hosts.jsx @@ -1570,6 +1570,7 @@ const BulkAssignModal = ({ isLoading, }) => { const [selectedGroupId, setSelectedGroupId] = useState(""); + const bulkHostGroupId = useId(); // Fetch host groups for selection const { data: hostGroups } = useQuery({ @@ -1588,28 +1589,31 @@ const BulkAssignModal = ({ return (
-
+
-

+

Assign to Host Group

-

+

Assigning {selectedHosts.length} host {selectedHosts.length !== 1 ? "s" : ""}:

-
+
{selectedHostNames.map((friendlyName) => ( -
+
• {friendlyName}
))} @@ -1620,7 +1624,7 @@ const BulkAssignModal = ({
@@ -1628,7 +1632,7 @@ const BulkAssignModal = ({ id={bulkHostGroupId} value={selectedGroupId} onChange={(e) => setSelectedGroupId(e.target.value)} - className="w-full px-3 py-2 border border-secondary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary-500" + className="w-full px-3 py-2 border border-secondary-300 dark:border-secondary-600 rounded-md bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500" > {hostGroups?.map((group) => ( @@ -1637,7 +1641,7 @@ const BulkAssignModal = ({ ))} -

+

Select a group to assign these hosts to, or leave ungrouped.

diff --git a/frontend/src/utils/osIcons.jsx b/frontend/src/utils/osIcons.jsx index 1bc6b42..a6c33e3 100644 --- a/frontend/src/utils/osIcons.jsx +++ b/frontend/src/utils/osIcons.jsx @@ -29,12 +29,11 @@ export const getOSIcon = (osType) => { os.includes("rhel") || os.includes("red hat") || os.includes("almalinux") || - os.includes("rocky") || - os === "ol" || - os.includes("oraclelinux") || - os.includes("oracle linux") + os.includes("rocky") ) return SiCentos; + if (os === "ol" || os.includes("oraclelinux") || os.includes("oracle linux")) + return SiLinux; // Use generic Linux icon for Oracle Linux if (os.includes("fedora")) return SiFedora; if (os.includes("arch")) return SiArchlinux; if (os.includes("alpine")) return SiAlpinelinux;