add newt version update available to table

This commit is contained in:
miloschwartz 2025-06-30 13:59:30 -07:00
parent 4ffdd6f74f
commit 1e5141c27c
No known key found for this signature in database
4 changed files with 98 additions and 7 deletions

View file

@ -1145,5 +1145,7 @@
"settingsErrorUpdate": "Failed to update settings",
"settingsErrorUpdateDescription": "An error occurred while updating settings",
"sidebarCollapse": "Collapse",
"sidebarExpand": "Expand"
"sidebarExpand": "Expand",
"newtUpdateAvailable": "Update Available",
"newtUpdateAvailableInfo": "A new version of Newt is available. Please update to the latest version for the best experience."
}

View file

@ -1,4 +1,4 @@
import { db } from "@server/db";
import { db, newts } from "@server/db";
import { orgs, roleSites, sites, userSites } from "@server/db";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
@ -9,6 +9,42 @@ import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import NodeCache from "node-cache";
import semver from "semver";
const newtVersionCache = new NodeCache({ stdTTL: 3600 }); // 1 hours in seconds
async function getLatestNewtVersion(): Promise<string | null> {
try {
const cachedVersion = newtVersionCache.get<string>("latestNewtVersion");
if (cachedVersion) {
return cachedVersion;
}
const response = await fetch(
"https://api.github.com/repos/fosrl/newt/tags"
);
if (!response.ok) {
logger.warn("Failed to fetch latest Newt version from GitHub");
return null;
}
const tags = await response.json();
if (!Array.isArray(tags) || tags.length === 0) {
logger.warn("No tags found for Newt repository");
return null;
}
const latestVersion = tags[0].name;
newtVersionCache.set("latestNewtVersion", latestVersion);
return latestVersion;
} catch (error) {
logger.error("Error fetching latest Newt version:", error);
return null;
}
}
const listSitesParamsSchema = z
.object({
@ -45,9 +81,11 @@ function querySites(orgId: string, accessibleSiteIds: number[]) {
type: sites.type,
online: sites.online,
address: sites.address,
newtVersion: newts.version
})
.from(sites)
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
.leftJoin(newts, eq(newts.siteId, sites.siteId))
.where(
and(
inArray(sites.siteId, accessibleSiteIds),
@ -56,8 +94,12 @@ function querySites(orgId: string, accessibleSiteIds: number[]) {
);
}
type SiteWithUpdateAvailable = Awaited<ReturnType<typeof querySites>>[0] & {
newtUpdateAvailable?: boolean;
};
export type ListSitesResponse = {
sites: Awaited<ReturnType<typeof querySites>>;
sites: SiteWithUpdateAvailable[];
pagination: { total: number; limit: number; offset: number };
};
@ -148,9 +190,36 @@ export async function listSites(
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
const latestNewtVersion = await getLatestNewtVersion();
const sitesWithUpdates: SiteWithUpdateAvailable[] = sitesList.map(
(site) => {
const siteWithUpdate: SiteWithUpdateAvailable = { ...site };
if (
site.type === "newt" &&
site.newtVersion &&
latestNewtVersion
) {
try {
siteWithUpdate.newtUpdateAvailable = semver.lt(
site.newtVersion,
latestNewtVersion
);
} catch (error) {
siteWithUpdate.newtUpdateAvailable = false;
}
} else {
siteWithUpdate.newtUpdateAvailable = false;
}
return siteWithUpdate;
}
);
return response<ListSitesResponse>(res, {
data: {
sites: sitesList,
sites: sitesWithUpdates,
pagination: {
total: totalCount,
limit,

View file

@ -29,6 +29,8 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
import CreateSiteFormModal from "./CreateSiteModal";
import { useTranslations } from "next-intl";
import { parseDataSize } from "@app/lib/dataSize";
import { Badge } from "@app/components/ui/badge";
import { InfoPopup } from "@app/components/ui/info-popup";
export type SiteRow = {
id: number;
@ -38,6 +40,8 @@ export type SiteRow = {
mbOut: string;
orgId: string;
type: "newt" | "wireguard";
newtVersion?: string;
newtUpdateAvailable?: boolean;
online: boolean;
address?: string;
};
@ -238,8 +242,22 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
if (originalRow.type === "newt") {
return (
<div className="flex items-center space-x-1">
<Badge variant="secondary">
<div className="flex items-center space-x-2">
<span>Newt</span>
{originalRow.newtVersion && (
<span className="text-xs text-gray-500">
v{originalRow.newtVersion}
</span>
)}
</div>
</Badge>
{originalRow.newtUpdateAvailable && (
<InfoPopup
info={t("newtUpdateAvailableInfo")}
/>
)}
</div>
);
}

View file

@ -49,7 +49,9 @@ export default async function SitesPage(props: SitesPageProps) {
mbOut: formatSize(site.megabytesOut || 0, site.type),
orgId: params.orgId,
type: site.type as any,
online: site.online
online: site.online,
newtVersion: site.newtVersion || undefined,
newtUpdateAvailable: site.newtUpdateAvailable || false,
};
});