Handle the agent and agent version

This commit is contained in:
Owen
2026-09-14 14:07:26 -04:00
parent 888ccce397
commit f9f38fb3f0
9 changed files with 91 additions and 81 deletions
+1
View File
@@ -153,6 +153,7 @@
"siteResourcesTargetsOnSite": "Targets on this site", "siteResourcesTargetsOnSite": "Targets on this site",
"siteSetting": "{siteName} Settings", "siteSetting": "{siteName} Settings",
"siteNewtTunnel": "Pangolin Site (Recommended)", "siteNewtTunnel": "Pangolin Site (Recommended)",
"pangolinSite": "Pangolin Site",
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.", "siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
"siteWg": "Basic WireGuard", "siteWg": "Basic WireGuard",
"siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.", "siteWgDescription": "Use any WireGuard client to establish a tunnel. Manual NAT setup required.",
+1 -1
View File
@@ -682,7 +682,7 @@ export const newts = pgTable(
secretHash: varchar("secretHash").notNull(), secretHash: varchar("secretHash").notNull(),
dateCreated: varchar("dateCreated").notNull(), dateCreated: varchar("dateCreated").notNull(),
version: varchar("version"), version: varchar("version"),
agent: varchar("agent").default("newt"), // either newt or cli agent: varchar("agent"), // either newt or cli
agentVersion: varchar("agentVersion"), agentVersion: varchar("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, { siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade" onDelete: "cascade"
+1 -1
View File
@@ -703,7 +703,7 @@ export const newts = sqliteTable(
secretHash: text("secretHash").notNull(), secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(), dateCreated: text("dateCreated").notNull(),
version: text("version"), version: text("version"),
agent: text("agent").default("newt"), // either newt or cli agent: text("agent"), // either newt or cli
agentVersion: text("agentVersion"), agentVersion: text("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, { siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade" onDelete: "cascade"
@@ -171,7 +171,11 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
logger.error(`Failed to add peer to exit node: ${error}`); logger.error(`Failed to add peer to exit node: ${error}`);
} }
if (newtVersion && newtVersion !== newt.version) { if (
newtVersion !== newt.version ||
agent !== newt.agent ||
agentVersion !== newt.agentVersion
) {
// update the newt version in the database // update the newt version in the database
await db await db
.update(newts) .update(newts)
@@ -19,7 +19,6 @@ import {
InfoSections, InfoSections,
InfoSectionTitle InfoSectionTitle
} from "@app/components/InfoSection"; } from "@app/components/InfoSection";
import { Badge } from "@app/components/ui/badge";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import ActionBanner from "@app/components/ActionBanner"; import ActionBanner from "@app/components/ActionBanner";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
@@ -41,13 +40,6 @@ import { useParams } from "next/navigation";
import { FaApple, FaWindows, FaLinux } from "react-icons/fa"; import { FaApple, FaWindows, FaLinux } from "react-icons/fa";
import { SiAndroid } from "react-icons/si"; import { SiAndroid } from "react-icons/si";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; 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 { function formatTimestamp(timestamp: number | null | undefined): string {
if (!timestamp) return "-"; if (!timestamp) return "-";
@@ -173,34 +165,6 @@ export default function GeneralPage() {
}>(null); }>(null);
const [isCheckingCache, setIsCheckingCache] = useState(false); const [isCheckingCache, setIsCheckingCache] = useState(false);
const [isRebuildingCache, setIsRebuildingCache] = 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 = Boolean(
semver.valid(client.olmVersion) &&
semver.lt(client.olmVersion, agentVersion.latestVersion)
);
}
}
// get "imp" from local storage to determine if we should show the verify button (imp = "1" means show) // get "imp" from local storage to determine if we should show the verify button (imp = "1" means show)
const showVerifyButton = const showVerifyButton =
@@ -467,7 +431,7 @@ export default function GeneralPage() {
)} )}
{/* Device Information Section */} {/* Device Information Section */}
{(client.fingerprint || (client.agent && client.olmVersion)) && ( {client.fingerprint && (
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
<SettingsSectionTitle> <SettingsSectionTitle>
@@ -479,33 +443,6 @@ export default function GeneralPage() {
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
{client.agent && client.olmVersion && (
<div className="mb-6">
<InfoSection>
<InfoSectionTitle>
{t("agent")}
</InfoSectionTitle>
<InfoSectionContent>
<div className="flex items-center">
<Badge variant="secondary">
{client.agent +
" v" +
client.olmVersion}
</Badge>
{updateAvailable && (
<InfoPopup
info={t(
"updateAvailableInfo"
)}
/>
)}
</div>
</InfoSectionContent>
</InfoSection>
</div>
)}
{client.fingerprint && {client.fingerprint &&
(() => { (() => {
const platform = client.fingerprint.platform; const platform = client.fingerprint.platform;
+61 -1
View File
@@ -11,9 +11,26 @@ import {
import IdpTypeBadge from "@app/components/IdpTypeBadge"; import IdpTypeBadge from "@app/components/IdpTypeBadge";
import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { getUserDisplayName } from "@app/lib/getUserDisplayName";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
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";
type ClientInfoCardProps = {}; type ClientInfoCardProps = {};
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"
};
export default function SiteInfoCard({}: ClientInfoCardProps) { export default function SiteInfoCard({}: ClientInfoCardProps) {
const { client, updateClient } = useClientContext(); const { client, updateClient } = useClientContext();
const t = useTranslations(); const t = useTranslations();
@@ -24,14 +41,57 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
username: client.userUsername username: client.userUsername
}); });
const data = useQuery(productUpdatesQueries.latestVersion(true));
const latestPlatformVersions = data.data?.data;
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 = Boolean(
semver.valid(client.olmVersion) &&
semver.lt(client.olmVersion, agentVersion.latestVersion)
);
}
}
const cols =
2 +
(userDisplayName ? 1 : 0) +
(client.agent && client.olmVersion ? 1 : 0);
return ( return (
<Alert> <Alert>
<AlertDescription> <AlertDescription>
<InfoSections cols={userDisplayName ? 3 : 2}> <InfoSections cols={cols}>
<InfoSection> <InfoSection>
<InfoSectionTitle>{t("name")}</InfoSectionTitle> <InfoSectionTitle>{t("name")}</InfoSectionTitle>
<InfoSectionContent>{client.name}</InfoSectionContent> <InfoSectionContent>{client.name}</InfoSectionContent>
</InfoSection> </InfoSection>
{client.agent && client.olmVersion ? (
<InfoSection>
<InfoSectionTitle>{t("agent")}</InfoSectionTitle>
<InfoSectionContent>
<div className="flex items-center gap-2">
<span>
{client.agent +
" v" +
client.olmVersion}
</span>
{updateAvailable && (
<InfoPopup
info={t("updateAvailableInfo")}
/>
)}
</div>
</InfoSectionContent>
</InfoSection>
) : null}
{userDisplayName ? ( {userDisplayName ? (
<InfoSection> <InfoSection>
<InfoSectionTitle>{t("user")}</InfoSectionTitle> <InfoSectionTitle>{t("user")}</InfoSectionTitle>
+6 -5
View File
@@ -63,7 +63,6 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
) : null; ) : null;
if (site.type === "newt") { if (site.type === "newt") {
const isCli = site.agent === "cli";
return ( return (
<Alert> <Alert>
<AlertDescription> <AlertDescription>
@@ -74,14 +73,16 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
{t("connectionType")} {t("connectionType")}
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{isCli ? "CLI" : "Newt"} {t("pangolinSite")}
</InfoSectionContent> </InfoSectionContent>
</InfoSection> </InfoSection>
<InfoSection> <InfoSection>
<InfoSectionTitle> <InfoSectionTitle>{t("agent")}</InfoSectionTitle>
{t("newtVersion")}
</InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
{site.agent == "newt" ? "Newt" : null}
{site.agent == "cli"
? "Pangolin CLI"
: null}{" "}
{site.agentVersion {site.agentVersion
? `v${site.agentVersion}` ? `v${site.agentVersion}`
: "-"} : "-"}
+10 -3
View File
@@ -371,7 +371,7 @@ export default function SitesTable({
}, },
{ {
accessorKey: "type", accessorKey: "type",
friendlyName: t("type"), friendlyName: t("agent"),
header: () => { header: () => {
return <span className="p-3">{t("type")}</span>; return <span className="p-3">{t("type")}</span>;
}, },
@@ -386,13 +386,20 @@ export default function SitesTable({
); );
if (originalRow.type === "newt") { if (originalRow.type === "newt") {
const isCli = originalRow.agent === "cli"; if (!originalRow.agent) {
return <span>-</span>;
}
return ( return (
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<Badge variant="secondary"> <Badge variant="secondary">
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<span> <span>
{isCli ? "CLI" : "Newt"} {originalRow.agent == "newt"
? "Newt"
: null}
{originalRow.agent == "cli"
? "Pangolin CLI"
: null}
</span> </span>
{originalRow.agentVersion && ( {originalRow.agentVersion && (
<span> <span>
+5 -5
View File
@@ -97,7 +97,7 @@ export function NewtSiteInstallCommands({
command: `${runAsRootPrefix}pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}` command: `${runAsRootPrefix}pangolin up site --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}`
} }
], ],
"Auto Systemd Service": [ "Systemd Service": [
{ {
title: t("install"), title: t("install"),
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash` command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
@@ -141,7 +141,7 @@ sudo chmod 600 /etc/pangolin/pangolin-site.env`
title: t("serviceFile"), title: t("serviceFile"),
command: `sudo tee /etc/systemd/system/pangolin-site.service > /dev/null << 'EOF' command: `sudo tee /etc/systemd/system/pangolin-site.service > /dev/null << 'EOF'
[Unit] [Unit]
Description=Newt Description=Pangolin Site
Wants=network-online.target Wants=network-online.target
After=network-online.target After=network-online.target
@@ -223,9 +223,9 @@ sudo systemctl enable --now pangolin-site`
docker: { docker: {
"Docker Compose": [ "Docker Compose": [
`services: `services:
newt: pangolin-site:
image: fosrl/pangolin-cli image: fosrl/pangolin-cli
container_name: newt container_name: pangolin-site
restart: unless-stopped restart: unless-stopped
environment: environment:
- PANGOLIN_ENDPOINT=${endpoint} - PANGOLIN_ENDPOINT=${endpoint}
@@ -521,7 +521,7 @@ function getPlatformName(platformName: Platform) {
function getArchitectures(platform: Platform) { function getArchitectures(platform: Platform) {
switch (platform) { switch (platform) {
case "linux": case "linux":
return ["Run", "Auto Systemd Service", "Manual Systemd Service"]; return ["Run", "Systemd Service", "Manual Systemd Service"];
case "macos": case "macos":
return ["Run", "Service"]; return ["Run", "Service"];
case "windows": case "windows":