Merge pull request #151 from PatchMon/fix/agentdata

Fix/agentdata
This commit is contained in:
9 Technology Group LTD
2025-10-08 16:25:56 +01:00
committed by GitHub
5 changed files with 239 additions and 139 deletions

View File

@@ -316,7 +316,7 @@ const Layout = ({ children }) => {
onClick={() => setSidebarOpen(false)}
aria-label="Close sidebar"
/>
<div className="relative flex w-full max-w-[280px] flex-col bg-white pb-4 pt-5 shadow-xl">
<div className="relative flex w-full max-w-[280px] flex-col bg-white dark:bg-secondary-800 pb-4 pt-5 shadow-xl">
<div className="absolute right-0 top-0 -mr-12 pt-2">
<button
type="button"
@@ -352,8 +352,8 @@ const Layout = ({ children }) => {
to={item.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(item.href)
? "bg-primary-100 text-primary-900"
: "text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900"
? "bg-primary-100 dark:bg-primary-600 text-primary-900 dark:text-white"
: "text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white"
}`}
onClick={() => setSidebarOpen(false)}
>
@@ -365,80 +365,82 @@ const Layout = ({ children }) => {
// Section with items
return (
<div key={item.section}>
<h3 className="text-xs font-semibold text-secondary-500 uppercase tracking-wider mb-2">
<h3 className="text-xs font-semibold text-secondary-500 dark:text-secondary-400 uppercase tracking-wider mb-2">
{item.section}
</h3>
<div className="space-y-1">
{item.items.map((subItem) => (
<div key={subItem.name}>
{subItem.name === "Hosts" && canManageHosts() ? (
// Special handling for Hosts item with integrated + button (mobile)
<Link
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 text-primary-900"
: "text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900"
}`}
onClick={() => setSidebarOpen(false)}
>
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2 flex-1">
{subItem.name}
{subItem.name === "Hosts" &&
stats?.cards?.totalHosts !== undefined && (
<span className="ml-2 inline-flex items-center justify-center px-1.5 py-0.5 text-xs rounded bg-secondary-100 text-secondary-700">
{stats.cards.totalHosts}
</span>
)}
</span>
<button
type="button"
onClick={(e) => {
e.preventDefault();
setSidebarOpen(false);
handleAddHost();
}}
className="ml-auto flex items-center justify-center w-5 h-5 rounded-full border-2 border-current opacity-60 hover:opacity-100 transition-all duration-200 self-center"
title="Add Host"
{item.items
.filter((subItem) => !subItem.comingSoon)
.map((subItem) => (
<div key={subItem.name}>
{subItem.name === "Hosts" && canManageHosts() ? (
// Special handling for Hosts item with integrated + button (mobile)
<Link
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 dark:bg-primary-600 text-primary-900 dark:text-white"
: "text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white"
}`}
onClick={() => setSidebarOpen(false)}
>
<Plus className="h-3 w-3" />
</button>
</Link>
) : (
// Standard navigation item (mobile)
<Link
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 text-primary-900"
: "text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900"
} ${subItem.comingSoon ? "opacity-50 cursor-not-allowed" : ""}`}
onClick={
subItem.comingSoon
? (e) => e.preventDefault()
: () => setSidebarOpen(false)
}
>
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2">
{subItem.name}
{subItem.name === "Hosts" &&
stats?.cards?.totalHosts !== undefined && (
<span className="ml-2 inline-flex items-center justify-center px-1.5 py-0.5 text-xs rounded bg-secondary-100 text-secondary-700">
{stats.cards.totalHosts}
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2 flex-1">
{subItem.name}
{subItem.name === "Hosts" &&
stats?.cards?.totalHosts !== undefined && (
<span className="ml-2 inline-flex items-center justify-center px-1.5 py-0.5 text-xs rounded bg-secondary-100 dark:bg-secondary-600 text-secondary-700 dark:text-secondary-200">
{stats.cards.totalHosts}
</span>
)}
</span>
<button
type="button"
onClick={(e) => {
e.preventDefault();
setSidebarOpen(false);
handleAddHost();
}}
className="ml-auto flex items-center justify-center w-5 h-5 rounded-full border-2 border-current opacity-60 hover:opacity-100 transition-all duration-200 self-center"
title="Add Host"
>
<Plus className="h-3 w-3" />
</button>
</Link>
) : (
// Standard navigation item (mobile)
<Link
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 dark:bg-primary-600 text-primary-900 dark:text-white"
: "text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white"
} ${subItem.comingSoon ? "opacity-50 cursor-not-allowed" : ""}`}
onClick={
subItem.comingSoon
? (e) => e.preventDefault()
: () => setSidebarOpen(false)
}
>
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2">
{subItem.name}
{subItem.name === "Hosts" &&
stats?.cards?.totalHosts !== undefined && (
<span className="ml-2 inline-flex items-center justify-center px-1.5 py-0.5 text-xs rounded bg-secondary-100 dark:bg-secondary-600 text-secondary-700 dark:text-secondary-200">
{stats.cards.totalHosts}
</span>
)}
{subItem.comingSoon && (
<span className="text-xs bg-secondary-100 dark:bg-secondary-600 text-secondary-600 dark:text-secondary-200 px-1.5 py-0.5 rounded">
Soon
</span>
)}
{subItem.comingSoon && (
<span className="text-xs bg-secondary-100 text-secondary-600 px-1.5 py-0.5 rounded">
Soon
</span>
)}
</span>
</Link>
)}
</div>
))}
</span>
</Link>
)}
</div>
))}
</div>
</div>
);
@@ -453,32 +455,70 @@ const Layout = ({ children }) => {
return (
<div key={item.section}>
<div className="space-y-1">
{item.items.map((subItem) => (
<Link
key={subItem.name}
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 text-primary-900"
: "text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900"
}`}
onClick={() => setSidebarOpen(false)}
>
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2">
{subItem.name}
{subItem.showUpgradeIcon && (
<UpgradeNotificationIcon className="h-3 w-3" />
)}
</span>
</Link>
))}
{item.items
.filter((subItem) => !subItem.comingSoon)
.map((subItem) => (
<Link
key={subItem.name}
to={subItem.href}
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive(subItem.href)
? "bg-primary-100 dark:bg-primary-600 text-primary-900 dark:text-white"
: "text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white"
}`}
onClick={() => setSidebarOpen(false)}
>
<subItem.icon className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2">
{subItem.name}
{subItem.showUpgradeIcon && (
<UpgradeNotificationIcon className="h-3 w-3" />
)}
</span>
</Link>
))}
</div>
</div>
);
}
return null;
})}
{/* Mobile Logout Section */}
<div className="mt-8 pt-4 border-t border-secondary-200 dark:border-secondary-700">
<div className="px-2 space-y-1">
<Link
to="/settings/profile"
className={`group flex items-center px-2 py-2 text-sm font-medium rounded-md ${
isActive("/settings/profile")
? "bg-primary-100 dark:bg-primary-600 text-primary-900 dark:text-white"
: "text-secondary-600 dark:text-secondary-300 hover:bg-secondary-50 dark:hover:bg-secondary-700 hover:text-secondary-900 dark:hover:text-white"
}`}
onClick={() => setSidebarOpen(false)}
>
<UserCircle className="mr-3 h-5 w-5" />
<span className="flex items-center gap-2">
{user?.first_name || user?.username}
{user?.role === "admin" && (
<span className="text-xs bg-secondary-100 dark:bg-secondary-600 text-secondary-600 dark:text-secondary-200 px-1.5 py-0.5 rounded">
Admin
</span>
)}
</span>
</Link>
<button
type="button"
onClick={() => {
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"
>
<LogOut className="mr-3 h-5 w-5" />
Sign out
</button>
</div>
</div>
</nav>
</div>
</div>
@@ -879,24 +919,29 @@ const Layout = ({ children }) => {
<div className="sticky top-0 z-40 flex h-16 shrink-0 items-center gap-x-4 border-b border-secondary-200 dark:border-secondary-600 bg-white dark:bg-secondary-800 px-4 shadow-sm sm:gap-x-6 sm:px-6 lg:px-8">
<button
type="button"
className="-m-2.5 p-2.5 text-secondary-700 lg:hidden"
className="-m-2.5 p-2.5 text-secondary-700 dark:text-white lg:hidden"
onClick={() => setSidebarOpen(true)}
>
<Menu className="h-6 w-6" />
</button>
{/* Separator */}
<div className="h-6 w-px bg-secondary-200 lg:hidden" />
<div className="h-6 w-px bg-secondary-200 dark:bg-secondary-600 lg:hidden" />
<div className="flex flex-1 gap-x-4 self-stretch lg:gap-x-6">
<div className="relative flex items-center">
<h2 className="text-lg font-semibold text-secondary-900 dark:text-secondary-100 whitespace-nowrap">
{getPageTitle()}
</h2>
</div>
{/* Page title - hidden on dashboard to give more space to search */}
{location.pathname !== "/" && (
<div className="relative flex items-center">
<h2 className="text-lg font-semibold text-secondary-900 dark:text-secondary-100 whitespace-nowrap">
{getPageTitle()}
</h2>
</div>
)}
{/* Global Search Bar */}
<div className="flex items-center max-w-sm">
<div
className={`flex items-center ${location.pathname === "/" ? "flex-1 max-w-none" : "max-w-sm"}`}
>
<GlobalSearch />
</div>

View File

@@ -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: {

View File

@@ -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 (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 w-full max-w-md">
<div className="bg-white dark:bg-secondary-800 rounded-lg p-6 w-full max-w-md">
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold text-secondary-900">
<h3 className="text-lg font-semibold text-secondary-900 dark:text-white">
Assign to Host Group
</h3>
<button
type="button"
onClick={onClose}
className="text-secondary-400 hover:text-secondary-600"
className="text-secondary-400 hover:text-secondary-600 dark:text-secondary-300 dark:hover:text-secondary-100"
>
<X className="h-5 w-5" />
</button>
</div>
<div className="mb-4">
<p className="text-sm text-secondary-600 mb-2">
<p className="text-sm text-secondary-600 dark:text-secondary-400 mb-2">
Assigning {selectedHosts.length} host
{selectedHosts.length !== 1 ? "s" : ""}:
</p>
<div className="max-h-32 overflow-y-auto bg-secondary-50 rounded-md p-3">
<div className="max-h-32 overflow-y-auto bg-secondary-50 dark:bg-secondary-700 rounded-md p-3">
{selectedHostNames.map((friendlyName) => (
<div key={friendlyName} className="text-sm text-secondary-700">
<div
key={friendlyName}
className="text-sm text-secondary-700 dark:text-secondary-300"
>
{friendlyName}
</div>
))}
@@ -1620,7 +1624,7 @@ const BulkAssignModal = ({
<div>
<label
htmlFor={bulkHostGroupId}
className="block text-sm font-medium text-secondary-700 mb-1"
className="block text-sm font-medium text-secondary-700 dark:text-secondary-300 mb-1"
>
Host Group
</label>
@@ -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"
>
<option value="">No group (ungrouped)</option>
{hostGroups?.map((group) => (
@@ -1637,7 +1641,7 @@ const BulkAssignModal = ({
</option>
))}
</select>
<p className="mt-1 text-sm text-secondary-500">
<p className="mt-1 text-sm text-secondary-500 dark:text-secondary-400">
Select a group to assign these hosts to, or leave ungrouped.
</p>
</div>

View File

@@ -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;