mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-22 23:31:49 +00:00
💄 Show updates available in the frontend, on sites & user devices
This commit is contained in:
@@ -2967,6 +2967,7 @@
|
||||
"orgOrDomainIdMissing": "Organization or Domain ID is missing",
|
||||
"loadingDNSRecords": "Loading DNS records...",
|
||||
"olmUpdateAvailableInfo": "An updated version of Olm is available. Please update to the latest version for the best experience.",
|
||||
"updateAvailableInfo": "An updated version is available. Please update to the latest version for the best experience.",
|
||||
"client": "Client",
|
||||
"proxyProtocol": "Proxy Protocol Settings",
|
||||
"proxyProtocolDescription": "Configure Proxy Protocol to preserve client IP addresses for TCP services.",
|
||||
|
||||
@@ -41,6 +41,13 @@ import { useParams } from "next/navigation";
|
||||
import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
|
||||
import { SiAndroid } from "react-icons/si";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import {
|
||||
productUpdatesQueries,
|
||||
type LatestVersionResponse
|
||||
} from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import semver from "semver";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
|
||||
function formatTimestamp(timestamp: number | null | undefined): string {
|
||||
if (!timestamp) return "-";
|
||||
@@ -166,6 +173,34 @@ export default function GeneralPage() {
|
||||
}>(null);
|
||||
const [isCheckingCache, setIsCheckingCache] = useState(false);
|
||||
const [isRebuildingCache, setIsRebuildingCache] = useState(false);
|
||||
const data = useQuery(productUpdatesQueries.latestVersion(true));
|
||||
const latestPlatformVersions = data.data?.data;
|
||||
|
||||
const agentVersionMap: Record<string, string> = {
|
||||
"Pangolin Windows": "windows",
|
||||
"Pangolin Android": "android",
|
||||
"Pangolin iOS": "ios",
|
||||
"Pangolin iPadOS": "ios",
|
||||
"Pangolin macOS": "mac",
|
||||
"Pangolin CLI": "cli",
|
||||
"Olm CLI": "olm"
|
||||
};
|
||||
|
||||
let updateAvailable = false;
|
||||
if (client.agent && client.olmVersion && latestPlatformVersions) {
|
||||
const agent = agentVersionMap[
|
||||
client.agent
|
||||
] as keyof LatestVersionResponse;
|
||||
|
||||
if (agent in latestPlatformVersions) {
|
||||
const agentVersion = latestPlatformVersions[agent];
|
||||
|
||||
updateAvailable = semver.lte(
|
||||
client.olmVersion,
|
||||
agentVersion.latestVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// get "imp" from local storage to determine if we should show the verify button (imp = "1" means show)
|
||||
const showVerifyButton =
|
||||
@@ -451,11 +486,21 @@ export default function GeneralPage() {
|
||||
{t("agent")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<Badge variant="secondary">
|
||||
{client.agent +
|
||||
" v" +
|
||||
client.olmVersion}
|
||||
</Badge>
|
||||
<div className="flex items-center">
|
||||
<Badge variant="secondary">
|
||||
{client.agent +
|
||||
" v" +
|
||||
client.olmVersion}
|
||||
</Badge>
|
||||
|
||||
{updateAvailable && (
|
||||
<InfoPopup
|
||||
info={t(
|
||||
"updateAvailableInfo"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
</div>
|
||||
|
||||
@@ -55,6 +55,9 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { LabelColumnFilterButton } from "./LabelColumnFilterButton";
|
||||
import { LabelsTableCell } from "./LabelsTableCell";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { productUpdatesQueries } from "@app/lib/queries";
|
||||
import semver from "semver";
|
||||
|
||||
export type SiteRow = {
|
||||
id: number;
|
||||
@@ -113,12 +116,11 @@ export default function SitesTable({
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
|
||||
// useEffect(() => {
|
||||
// const interval = setInterval(() => {
|
||||
// router.refresh();
|
||||
// }, 30_000);
|
||||
// return () => clearInterval(interval);
|
||||
// }, []);
|
||||
const { data: latestVersions } = useQuery(
|
||||
productUpdatesQueries.latestVersion(true)
|
||||
);
|
||||
|
||||
const latestNewtVersion = latestVersions?.data?.newt?.latestVersion;
|
||||
|
||||
const booleanSearchFilterSchema = z
|
||||
.enum(["true", "false"])
|
||||
@@ -333,6 +335,11 @@ export default function SitesTable({
|
||||
cell: ({ row }) => {
|
||||
const originalRow = row.original;
|
||||
|
||||
let updateAvailable =
|
||||
latestNewtVersion &&
|
||||
originalRow.newtVersion &&
|
||||
semver.lt(originalRow.newtVersion, latestNewtVersion);
|
||||
|
||||
if (originalRow.type === "newt") {
|
||||
return (
|
||||
<div className="flex items-center space-x-1">
|
||||
@@ -346,7 +353,7 @@ export default function SitesTable({
|
||||
)}
|
||||
</div>
|
||||
</Badge>
|
||||
{originalRow.newtUpdateAvailable && (
|
||||
{updateAvailable && (
|
||||
<InfoPopup
|
||||
info={t("newtUpdateAvailableInfo")}
|
||||
/>
|
||||
@@ -561,7 +568,7 @@ export default function SitesTable({
|
||||
}
|
||||
|
||||
return cols;
|
||||
}, [isLabelFeatureEnabled, orgId, t, searchParams]);
|
||||
}, [isLabelFeatureEnabled, orgId, t, searchParams, latestNewtVersion]);
|
||||
|
||||
function toggleSort(column: string) {
|
||||
const newSearch = getNextSortOrder(column, searchParams);
|
||||
|
||||
@@ -38,6 +38,12 @@ import { ColumnFilterButton } from "./ColumnFilterButton";
|
||||
import IdpTypeBadge from "./IdpTypeBadge";
|
||||
import { Badge } from "./ui/badge";
|
||||
import { ControlledDataTable } from "./ui/controlled-data-table";
|
||||
import {
|
||||
productUpdatesQueries,
|
||||
type LatestVersionResponse
|
||||
} from "@app/lib/queries";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import semver from "semver";
|
||||
|
||||
export type ClientRow = {
|
||||
id: number;
|
||||
@@ -100,6 +106,9 @@ export default function UserDevicesTable({
|
||||
searchParams
|
||||
} = useNavigationContext();
|
||||
const [isRefreshing, startTransition] = useTransition();
|
||||
const data = useQuery(productUpdatesQueries.latestVersion(true));
|
||||
|
||||
const latestPlatformVersions = data.data?.data;
|
||||
|
||||
const defaultUserColumnVisibility = {
|
||||
subnet: false,
|
||||
@@ -555,6 +564,37 @@ export default function UserDevicesTable({
|
||||
cell: ({ row }) => {
|
||||
const originalRow = row.original;
|
||||
|
||||
const agentVersionMap: Record<string, string> = {
|
||||
"Pangolin Windows": "windows",
|
||||
"Pangolin Android": "android",
|
||||
"Pangolin iOS": "ios",
|
||||
"Pangolin iPadOS": "ios",
|
||||
"Pangolin macOS": "mac",
|
||||
"Pangolin CLI": "cli",
|
||||
"Olm CLI": "olm"
|
||||
};
|
||||
|
||||
let updateAvailable = false;
|
||||
|
||||
if (
|
||||
originalRow.olmVersion &&
|
||||
originalRow.agent &&
|
||||
latestPlatformVersions
|
||||
) {
|
||||
const agent = agentVersionMap[
|
||||
originalRow.agent
|
||||
] as keyof LatestVersionResponse;
|
||||
|
||||
if (agent in latestPlatformVersions) {
|
||||
const agentVersion = latestPlatformVersions[agent];
|
||||
|
||||
updateAvailable = semver.lt(
|
||||
originalRow.olmVersion,
|
||||
agentVersion.latestVersion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-1">
|
||||
{originalRow.agent && originalRow.olmVersion ? (
|
||||
@@ -567,9 +607,9 @@ export default function UserDevicesTable({
|
||||
"-"
|
||||
)}
|
||||
|
||||
{/*originalRow.olmUpdateAvailable && (
|
||||
<InfoPopup info={t("olmUpdateAvailableInfo")} />
|
||||
)*/}
|
||||
{updateAvailable && (
|
||||
<InfoPopup info={t("updateAvailableInfo")} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -714,7 +754,7 @@ export default function UserDevicesTable({
|
||||
}
|
||||
|
||||
return allOptions;
|
||||
}, [t]);
|
||||
}, [t, latestPlatformVersions]);
|
||||
|
||||
function handleFilterChange(
|
||||
column: string,
|
||||
|
||||
Reference in New Issue
Block a user