mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
add: agent update notifier, pagination
This commit is contained in:
@@ -66,7 +66,7 @@ export default async function RoutePage(props: PageParams<{}>) {
|
|||||||
organizationSlug={organization.slug}
|
organizationSlug={organization.slug}
|
||||||
data={projects}
|
data={projects}
|
||||||
cardItem={ProjectCard}
|
cardItem={ProjectCard}
|
||||||
cardsPerPage={6}
|
cardsPerPage={9}
|
||||||
numberOfColumns={3}
|
numberOfColumns={3}
|
||||||
/>
|
/>
|
||||||
) : isMember ? (
|
) : isMember ? (
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export type paginationNavigationProps = {
|
|||||||
export const PaginationNavigation = (props: paginationNavigationProps) => {
|
export const PaginationNavigation = (props: paginationNavigationProps) => {
|
||||||
const { className, totalPages, currentPage, goToPage, goToPrevPage, goToNextPage, maxVisiblePages = 3 } = props;
|
const { className, totalPages, currentPage, goToPage, goToPrevPage, goToNextPage, maxVisiblePages = 3 } = props;
|
||||||
|
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Pagination className={cn("", className)}>
|
<Pagination className={cn("", className)}>
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
|
|||||||
@@ -6,10 +6,12 @@ import {Card} from "@/components/ui/card";
|
|||||||
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
|
import {ConnectionIndicator} from "@/components/wrappers/common/connection-indicator";
|
||||||
import {formatDateLastContact} from "@/utils/date-formatting";
|
import {formatDateLastContact} from "@/utils/date-formatting";
|
||||||
import {AgentWith} from "@/db/schema/08_agent";
|
import {AgentWith} from "@/db/schema/08_agent";
|
||||||
import {Activity, ChevronRight, Copy, Check, Fingerprint, Server, Database} from "lucide-react";
|
import {Activity, ChevronRight, Copy, Check, Fingerprint, Server, Database, AlertCircle} from "lucide-react";
|
||||||
import {Badge} from "@/components/ui/badge";
|
import {Badge} from "@/components/ui/badge";
|
||||||
import {truncateWords} from "@/utils/text";
|
import {truncateWords} from "@/utils/text";
|
||||||
import {useIsMobile} from "@/hooks/use-mobile";
|
import {useIsMobile} from "@/hooks/use-mobile";
|
||||||
|
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
|
||||||
|
import {useAgentUpdateCheck} from "@/features/agents/hooks/use-agent-update-check";
|
||||||
|
|
||||||
export type agentCardProps = {
|
export type agentCardProps = {
|
||||||
data: AgentWith;
|
data: AgentWith;
|
||||||
@@ -20,6 +22,8 @@ export const AgentCard = (props: agentCardProps) => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const [isCopied, setIsCopied] = useState(false);
|
const [isCopied, setIsCopied] = useState(false);
|
||||||
|
|
||||||
|
const {newRelease, isUpdateAvailable} = useAgentUpdateCheck(agent.version);
|
||||||
|
|
||||||
const handleCopy = (e: React.MouseEvent) => {
|
const handleCopy = (e: React.MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -44,9 +48,23 @@ export const AgentCard = (props: agentCardProps) => {
|
|||||||
{isMobile ? truncateWords(agent.name, 2) : agent.name}
|
{isMobile ? truncateWords(agent.name, 2) : agent.name}
|
||||||
</h3>
|
</h3>
|
||||||
{agent.version && (
|
{agent.version && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
<Badge variant="secondary" className="h-5 px-1.5 text-[10px] font-bold tracking-wider">
|
<Badge variant="secondary" className="h-5 px-1.5 text-[10px] font-bold tracking-wider">
|
||||||
v{agent.version}
|
v{agent.version}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{isUpdateAvailable && (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex items-center justify-center w-5 h-5 bg-yellow-500/10 rounded-full border border-yellow-500/20 text-yellow-600">
|
||||||
|
<AlertCircle className="w-3 h-3" />
|
||||||
|
</div>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Update available: {newRelease?.tag_name}</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {useQuery} from "@tanstack/react-query";
|
||||||
|
import {getNewAgentRelease} from "@/features/updates/services/github";
|
||||||
|
|
||||||
|
export const useAgentUpdateCheck = (currentVersion?: string | null) => {
|
||||||
|
const {data: newRelease, isLoading} = useQuery({
|
||||||
|
queryKey: ["agent-new-release", currentVersion],
|
||||||
|
queryFn: () => currentVersion ? getNewAgentRelease(currentVersion) : Promise.resolve(null),
|
||||||
|
staleTime: 1000 * 60 * 60,
|
||||||
|
enabled: !!currentVersion,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
newRelease,
|
||||||
|
isLoading,
|
||||||
|
isUpdateAvailable: !!newRelease,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -14,7 +14,7 @@ type ParsedVersion = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function parseVersion(version: string): ParsedVersion {
|
function parseVersion(version: string): ParsedVersion {
|
||||||
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?$/);
|
const match = version.match(/^v?(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?$/);
|
||||||
if (!match) return {
|
if (!match) return {
|
||||||
major: 0,
|
major: 0,
|
||||||
minor: 0,
|
minor: 0,
|
||||||
@@ -30,6 +30,49 @@ function parseVersion(version: string): ParsedVersion {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findLatestVersion = (currentVersion: string, releases: GitHubRelease[]): GitHubRelease | null => {
|
||||||
|
const cleanCurrentVersion = currentVersion.replace(/^v/, "");
|
||||||
|
const isStable = /^\d+\.\d+\.\d+$/.test(cleanCurrentVersion);
|
||||||
|
const isRc = /^\d+\.\d+\.\d+-rc\.\d+$/.test(cleanCurrentVersion);
|
||||||
|
|
||||||
|
const currentParsedVersion = parseVersion(cleanCurrentVersion);
|
||||||
|
|
||||||
|
if (isRc) {
|
||||||
|
const latestStableVersion = releases.find(r => !r.prerelease);
|
||||||
|
const latestRcVersion = releases.find(r => r.prerelease && (r.tag_name.includes("rc") || (r.name && r.name.includes("rc"))));
|
||||||
|
|
||||||
|
if (latestStableVersion) {
|
||||||
|
const versionStr = latestStableVersion.tag_name || latestStableVersion.name;
|
||||||
|
const latestStableParsedVersion = parseVersion(versionStr);
|
||||||
|
if (latestStableParsedVersion.major > currentParsedVersion.major) return latestStableVersion;
|
||||||
|
if (latestStableParsedVersion.major === currentParsedVersion.major && latestStableParsedVersion.minor > currentParsedVersion.minor) return latestStableVersion;
|
||||||
|
if (latestStableParsedVersion.major === currentParsedVersion.major && latestStableParsedVersion.minor === currentParsedVersion.minor && latestStableParsedVersion.patch >= currentParsedVersion.patch) return latestStableVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestRcVersion) {
|
||||||
|
const versionStr = latestRcVersion.tag_name || latestRcVersion.name;
|
||||||
|
const latestRcParsedVersion = parseVersion(versionStr);
|
||||||
|
if (latestRcParsedVersion.major > currentParsedVersion.major) return latestRcVersion;
|
||||||
|
if (latestRcParsedVersion.major === currentParsedVersion.major && latestRcParsedVersion.minor > currentParsedVersion.minor) return latestRcVersion;
|
||||||
|
if (latestRcParsedVersion.major === currentParsedVersion.major && latestRcParsedVersion.minor === currentParsedVersion.minor && latestRcParsedVersion.patch > currentParsedVersion.patch) return latestRcVersion;
|
||||||
|
if (latestRcParsedVersion.major === currentParsedVersion.major && latestRcParsedVersion.minor === currentParsedVersion.minor && latestRcParsedVersion.patch === currentParsedVersion.patch &&
|
||||||
|
latestRcParsedVersion.rc !== undefined && currentParsedVersion.rc !== undefined && latestRcParsedVersion.rc > currentParsedVersion.rc) return latestRcVersion;
|
||||||
|
}
|
||||||
|
} else if (isStable) {
|
||||||
|
const latestStableVersion = releases.find(r => !r.prerelease);
|
||||||
|
if (latestStableVersion) {
|
||||||
|
const versionStr = latestStableVersion.tag_name || latestStableVersion.name;
|
||||||
|
const latestStableParsedVersion = parseVersion(versionStr);
|
||||||
|
|
||||||
|
if (latestStableParsedVersion.major > currentParsedVersion.major) return latestStableVersion;
|
||||||
|
if (latestStableParsedVersion.major === currentParsedVersion.major && latestStableParsedVersion.minor > currentParsedVersion.minor) return latestStableVersion;
|
||||||
|
if (latestStableParsedVersion.major === currentParsedVersion.major && latestStableParsedVersion.minor === currentParsedVersion.minor && latestStableParsedVersion.patch > currentParsedVersion.patch) return latestStableVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const getNewRelease = async (currentVersion: string): Promise<GitHubRelease | null> => {
|
export const getNewRelease = async (currentVersion: string): Promise<GitHubRelease | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch("https://api.github.com/repos/Portabase/portabase/releases");
|
const response = await fetch("https://api.github.com/repos/Portabase/portabase/releases");
|
||||||
@@ -38,49 +81,28 @@ export const getNewRelease = async (currentVersion: string): Promise<GitHubRelea
|
|||||||
}
|
}
|
||||||
|
|
||||||
const releases: GitHubRelease[] = await response.json();
|
const releases: GitHubRelease[] = await response.json();
|
||||||
|
return findLatestVersion(currentVersion, releases);
|
||||||
const isStable = /^\d+\.\d+\.\d+$/.test(currentVersion);
|
|
||||||
const isRc = /^\d+\.\d+\.\d+-rc\.\d+$/.test(currentVersion);
|
|
||||||
|
|
||||||
console.log("isStable", isStable)
|
|
||||||
console.log("isRc", isRc)
|
|
||||||
|
|
||||||
|
|
||||||
const currentParsedVersion = parseVersion(currentVersion)
|
|
||||||
|
|
||||||
const latestStableVersion = releases.find(r => !r.prerelease)
|
|
||||||
const latestRcVersion = releases.find(r => r.name.includes("rc"))!
|
|
||||||
|
|
||||||
if (isRc) {
|
|
||||||
if (!latestRcVersion) return null
|
|
||||||
const latestRcParsedVersion = parseVersion(latestRcVersion.name)
|
|
||||||
if (currentParsedVersion.major < latestRcParsedVersion.major) return latestRcVersion;
|
|
||||||
if (currentParsedVersion.minor < latestRcParsedVersion.minor) return latestRcVersion;
|
|
||||||
if (currentParsedVersion.patch < latestRcParsedVersion.patch) return latestRcVersion;
|
|
||||||
if (currentParsedVersion.rc! < latestRcParsedVersion.rc!) return latestRcVersion;
|
|
||||||
|
|
||||||
if (!latestStableVersion) return null
|
|
||||||
const latestStableParsedVersion = parseVersion(latestStableVersion.name)
|
|
||||||
if (currentParsedVersion.major < latestStableParsedVersion.major) return latestStableVersion;
|
|
||||||
if (currentParsedVersion.minor < latestStableParsedVersion.minor) return latestStableVersion;
|
|
||||||
if (currentParsedVersion.patch < latestStableParsedVersion.patch) return latestStableVersion;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} else if (isStable) {
|
|
||||||
if (!latestStableVersion) return null
|
|
||||||
const latestStableParsedVersion = parseVersion(latestStableVersion.name)
|
|
||||||
if (currentParsedVersion.major < latestStableParsedVersion.major) return latestStableVersion;
|
|
||||||
if (currentParsedVersion.minor < latestStableParsedVersion.minor) return latestStableVersion;
|
|
||||||
if (currentParsedVersion.patch < latestStableParsedVersion.patch) return latestStableVersion;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to fetch latest release", error);
|
console.error("Failed to fetch latest release", error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNewAgentRelease = async (currentVersion: string): Promise<GitHubRelease | null> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("https://api.github.com/repos/Portabase/agent-rust/releases", {
|
||||||
|
next: { revalidate: 3600 }
|
||||||
|
});
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const releases: GitHubRelease[] = await response.json();
|
||||||
|
return findLatestVersion(currentVersion, releases);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch latest agent release", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user