mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-03 09:16:40 +00:00
client olm version show in the table
This commit is contained in:
@@ -1891,5 +1891,7 @@
|
|||||||
"cannotbeUndone": "This can not be undone.",
|
"cannotbeUndone": "This can not be undone.",
|
||||||
"toConfirm": "to confirm",
|
"toConfirm": "to confirm",
|
||||||
"deleteClientQuestion": "Are you sure you want to remove the client from the site and organization?",
|
"deleteClientQuestion": "Are you sure you want to remove the client from the site and organization?",
|
||||||
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site."
|
"clientMessageRemove": "Once removed, the client will no longer be able to connect to the site.",
|
||||||
|
"olmUpdateAvailableInfo": "An updated version of Olm is available. Please update to the latest version for the best experience.",
|
||||||
|
"client": "Client"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { db } from "@server/db";
|
import { db, olms } from "@server/db";
|
||||||
import {
|
import {
|
||||||
clients,
|
clients,
|
||||||
orgs,
|
orgs,
|
||||||
@@ -16,6 +16,67 @@ import createHttpError from "http-errors";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import NodeCache from "node-cache";
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
|
const olmVersionCache = new NodeCache({ stdTTL: 3600 });
|
||||||
|
|
||||||
|
async function getLatestOlmVersion(): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const cachedVersion = olmVersionCache.get<string>("latestOlmVersion");
|
||||||
|
if (cachedVersion) {
|
||||||
|
return cachedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), 1500);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
"https://api.github.com/repos/fosrl/olm/tags",
|
||||||
|
{
|
||||||
|
signal: controller.signal
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
logger.warn(
|
||||||
|
`Failed to fetch latest Olm version from GitHub: ${response.status} ${response.statusText}`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = await response.json();
|
||||||
|
if (!Array.isArray(tags) || tags.length === 0) {
|
||||||
|
logger.warn("No tags found for Olm repository");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestVersion = tags[0].name;
|
||||||
|
|
||||||
|
olmVersionCache.set("latestOlmVersion", latestVersion);
|
||||||
|
|
||||||
|
return latestVersion;
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name === "AbortError") {
|
||||||
|
logger.warn(
|
||||||
|
"Request to fetch latest Olm version timed out (1.5s)"
|
||||||
|
);
|
||||||
|
} else if (error.cause?.code === "UND_ERR_CONNECT_TIMEOUT") {
|
||||||
|
logger.warn(
|
||||||
|
"Connection timeout while fetching latest Olm version"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"Error fetching latest Olm version:",
|
||||||
|
error.message || error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
const listClientsParamsSchema = z
|
const listClientsParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -50,10 +111,12 @@ function queryClients(orgId: string, accessibleClientIds: number[]) {
|
|||||||
megabytesOut: clients.megabytesOut,
|
megabytesOut: clients.megabytesOut,
|
||||||
orgName: orgs.name,
|
orgName: orgs.name,
|
||||||
type: clients.type,
|
type: clients.type,
|
||||||
online: clients.online
|
online: clients.online,
|
||||||
|
olmVersion: olms.version
|
||||||
})
|
})
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
||||||
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(clients.clientId, accessibleClientIds),
|
inArray(clients.clientId, accessibleClientIds),
|
||||||
@@ -77,12 +140,20 @@ async function getSiteAssociations(clientIds: number[]) {
|
|||||||
.where(inArray(clientSites.clientId, clientIds));
|
.where(inArray(clientSites.clientId, clientIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OlmWithUpdateAvailable = Awaited<ReturnType<typeof queryClients>>[0] & {
|
||||||
|
olmUpdateAvailable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export type ListClientsResponse = {
|
export type ListClientsResponse = {
|
||||||
clients: Array<Awaited<ReturnType<typeof queryClients>>[0] & { sites: Array<{
|
clients: Array<Awaited<ReturnType<typeof queryClients>>[0] & {
|
||||||
siteId: number;
|
sites: Array<{
|
||||||
siteName: string | null;
|
siteId: number;
|
||||||
siteNiceId: string | null;
|
siteName: string | null;
|
||||||
}> }>;
|
siteNiceId: string | null;
|
||||||
|
}>
|
||||||
|
olmUpdateAvailable?: boolean;
|
||||||
|
}>;
|
||||||
pagination: { total: number; limit: number; offset: number };
|
pagination: { total: number; limit: number; offset: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,6 +277,43 @@ export async function listClients(
|
|||||||
sites: sitesByClient[client.clientId] || []
|
sites: sitesByClient[client.clientId] || []
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const latestOlVersionPromise = getLatestOlmVersion();
|
||||||
|
|
||||||
|
const olmsWithUpdates: OlmWithUpdateAvailable[] = clientsWithSites.map(
|
||||||
|
(client) => {
|
||||||
|
const OlmWithUpdate: OlmWithUpdateAvailable = { ...client };
|
||||||
|
// Initially set to false, will be updated if version check succeeds
|
||||||
|
OlmWithUpdate.olmUpdateAvailable = false;
|
||||||
|
return OlmWithUpdate;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Try to get the latest version, but don't block if it fails
|
||||||
|
try {
|
||||||
|
const latestOlVersion = await latestOlVersionPromise;
|
||||||
|
|
||||||
|
if (latestOlVersion) {
|
||||||
|
olmsWithUpdates.forEach((client) => {
|
||||||
|
try {
|
||||||
|
client.olmUpdateAvailable = semver.lt(
|
||||||
|
client.olmVersion ? client.olmVersion : "",
|
||||||
|
latestOlVersion
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
client.olmUpdateAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Log the error but don't let it block the response
|
||||||
|
logger.warn(
|
||||||
|
"Failed to check for OLM updates, continuing without update info:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return response<ListClientsResponse>(res, {
|
return response<ListClientsResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
clients: clientsWithSites,
|
clients: clientsWithSites,
|
||||||
|
|||||||
@@ -44,7 +44,9 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
mbIn: formatSize(client.megabytesIn || 0),
|
mbIn: formatSize(client.megabytesIn || 0),
|
||||||
mbOut: formatSize(client.megabytesOut || 0),
|
mbOut: formatSize(client.megabytesOut || 0),
|
||||||
orgId: params.orgId,
|
orgId: params.orgId,
|
||||||
online: client.online
|
online: client.online,
|
||||||
|
olmVersion: client.olmVersion || undefined,
|
||||||
|
olmUpdateAvailable: client.olmUpdateAvailable || false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import { formatAxiosError } from "@app/lib/api";
|
|||||||
import { createApiClient } from "@app/lib/api";
|
import { createApiClient } from "@app/lib/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Badge } from "./ui/badge";
|
||||||
|
import { InfoPopup } from "./ui/info-popup";
|
||||||
|
|
||||||
export type ClientRow = {
|
export type ClientRow = {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -36,6 +38,8 @@ export type ClientRow = {
|
|||||||
mbOut: string;
|
mbOut: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
online: boolean;
|
online: boolean;
|
||||||
|
olmVersion?: string;
|
||||||
|
olmUpdateAvailable: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClientTableProps = {
|
type ClientTableProps = {
|
||||||
@@ -204,6 +208,45 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "client",
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
column.toggleSorting(column.getIsSorted() === "asc")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("client")}
|
||||||
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const originalRow = row.original;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<Badge variant="secondary">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<span>Olm</span>
|
||||||
|
{originalRow.olmVersion && (
|
||||||
|
<span className="text-xs text-gray-500">
|
||||||
|
v{originalRow.olmVersion}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Badge>
|
||||||
|
{originalRow.olmUpdateAvailable && (
|
||||||
|
<InfoPopup
|
||||||
|
info={t("olmUpdateAvailableInfo")}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "subnet",
|
accessorKey: "subnet",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
@@ -282,7 +325,7 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) {
|
|||||||
{t("deleteClientQuestion")}
|
{t("deleteClientQuestion")}
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
{t("clientMessageRemove")}
|
{t("clientMessageRemove")}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user