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",
"siteSetting": "{siteName} Settings",
"siteNewtTunnel": "Pangolin Site (Recommended)",
"pangolinSite": "Pangolin Site",
"siteNewtTunnelDescription": "Easiest way to create an entrypoint into any network. No extra setup.",
"siteWg": "Basic WireGuard",
"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(),
dateCreated: varchar("dateCreated").notNull(),
version: varchar("version"),
agent: varchar("agent").default("newt"), // either newt or cli
agent: varchar("agent"), // either newt or cli
agentVersion: varchar("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
+1 -1
View File
@@ -703,7 +703,7 @@ export const newts = sqliteTable(
secretHash: text("secretHash").notNull(),
dateCreated: text("dateCreated").notNull(),
version: text("version"),
agent: text("agent").default("newt"), // either newt or cli
agent: text("agent"), // either newt or cli
agentVersion: text("agentVersion"),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
@@ -171,7 +171,11 @@ export const handleNewtRegisterMessage: MessageHandler = async (context) => {
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
await db
.update(newts)
@@ -19,7 +19,6 @@ import {
InfoSections,
InfoSectionTitle
} from "@app/components/InfoSection";
import { Badge } from "@app/components/ui/badge";
import { Button } from "@app/components/ui/button";
import ActionBanner from "@app/components/ActionBanner";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
@@ -41,13 +40,6 @@ 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 "-";
@@ -173,34 +165,6 @@ 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 = 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)
const showVerifyButton =
@@ -467,7 +431,7 @@ export default function GeneralPage() {
)}
{/* Device Information Section */}
{(client.fingerprint || (client.agent && client.olmVersion)) && (
{client.fingerprint && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
@@ -479,33 +443,6 @@ export default function GeneralPage() {
</SettingsSectionHeader>
<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 &&
(() => {
const platform = client.fingerprint.platform;
+61 -1
View File
@@ -11,9 +11,26 @@ import {
import IdpTypeBadge from "@app/components/IdpTypeBadge";
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
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 = {};
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) {
const { client, updateClient } = useClientContext();
const t = useTranslations();
@@ -24,14 +41,57 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
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 (
<Alert>
<AlertDescription>
<InfoSections cols={userDisplayName ? 3 : 2}>
<InfoSections cols={cols}>
<InfoSection>
<InfoSectionTitle>{t("name")}</InfoSectionTitle>
<InfoSectionContent>{client.name}</InfoSectionContent>
</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 ? (
<InfoSection>
<InfoSectionTitle>{t("user")}</InfoSectionTitle>
+6 -5
View File
@@ -63,7 +63,6 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
) : null;
if (site.type === "newt") {
const isCli = site.agent === "cli";
return (
<Alert>
<AlertDescription>
@@ -74,14 +73,16 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
{t("connectionType")}
</InfoSectionTitle>
<InfoSectionContent>
{isCli ? "CLI" : "Newt"}
{t("pangolinSite")}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("newtVersion")}
</InfoSectionTitle>
<InfoSectionTitle>{t("agent")}</InfoSectionTitle>
<InfoSectionContent>
{site.agent == "newt" ? "Newt" : null}
{site.agent == "cli"
? "Pangolin CLI"
: null}{" "}
{site.agentVersion
? `v${site.agentVersion}`
: "-"}
+10 -3
View File
@@ -371,7 +371,7 @@ export default function SitesTable({
},
{
accessorKey: "type",
friendlyName: t("type"),
friendlyName: t("agent"),
header: () => {
return <span className="p-3">{t("type")}</span>;
},
@@ -386,13 +386,20 @@ export default function SitesTable({
);
if (originalRow.type === "newt") {
const isCli = originalRow.agent === "cli";
if (!originalRow.agent) {
return <span>-</span>;
}
return (
<div className="flex items-center space-x-1">
<Badge variant="secondary">
<div className="flex items-center space-x-1">
<span>
{isCli ? "CLI" : "Newt"}
{originalRow.agent == "newt"
? "Newt"
: null}
{originalRow.agent == "cli"
? "Pangolin CLI"
: null}
</span>
{originalRow.agentVersion && (
<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}`
}
],
"Auto Systemd Service": [
"Systemd Service": [
{
title: t("install"),
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"),
command: `sudo tee /etc/systemd/system/pangolin-site.service > /dev/null << 'EOF'
[Unit]
Description=Newt
Description=Pangolin Site
Wants=network-online.target
After=network-online.target
@@ -223,9 +223,9 @@ sudo systemctl enable --now pangolin-site`
docker: {
"Docker Compose": [
`services:
newt:
pangolin-site:
image: fosrl/pangolin-cli
container_name: newt
container_name: pangolin-site
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=${endpoint}
@@ -521,7 +521,7 @@ function getPlatformName(platformName: Platform) {
function getArchitectures(platform: Platform) {
switch (platform) {
case "linux":
return ["Run", "Auto Systemd Service", "Manual Systemd Service"];
return ["Run", "Systemd Service", "Manual Systemd Service"];
case "macos":
return ["Run", "Service"];
case "windows":