mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-23 05:16:38 +00:00
🚧 wip: user devices endpoint
This commit is contained in:
@@ -6,6 +6,7 @@ export * from "./unarchiveClient";
|
|||||||
export * from "./blockClient";
|
export * from "./blockClient";
|
||||||
export * from "./unblockClient";
|
export * from "./unblockClient";
|
||||||
export * from "./listClients";
|
export * from "./listClients";
|
||||||
|
export * from "./listUserDevices";
|
||||||
export * from "./updateClient";
|
export * from "./updateClient";
|
||||||
export * from "./getClient";
|
export * from "./getClient";
|
||||||
export * from "./createUserClient";
|
export * from "./createUserClient";
|
||||||
|
|||||||
@@ -105,11 +105,7 @@ const listClientsSchema = z.object({
|
|||||||
.catch(1)
|
.catch(1)
|
||||||
.default(1),
|
.default(1),
|
||||||
query: z.string().optional(),
|
query: z.string().optional(),
|
||||||
sort_by: z
|
sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined)
|
||||||
.enum(["megabytesIn", "megabytesOut"])
|
|
||||||
.optional()
|
|
||||||
.catch(undefined),
|
|
||||||
filter: z.enum(["user", "machine"]).optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryClientsBase() {
|
function queryClientsBase() {
|
||||||
@@ -134,15 +130,7 @@ function queryClientsBase() {
|
|||||||
approvalState: clients.approvalState,
|
approvalState: clients.approvalState,
|
||||||
olmArchived: olms.archived,
|
olmArchived: olms.archived,
|
||||||
archived: clients.archived,
|
archived: clients.archived,
|
||||||
blocked: clients.blocked,
|
blocked: clients.blocked
|
||||||
deviceModel: currentFingerprint.deviceModel,
|
|
||||||
fingerprintPlatform: currentFingerprint.platform,
|
|
||||||
fingerprintOsVersion: currentFingerprint.osVersion,
|
|
||||||
fingerprintKernelVersion: currentFingerprint.kernelVersion,
|
|
||||||
fingerprintArch: currentFingerprint.arch,
|
|
||||||
fingerprintSerialNumber: currentFingerprint.serialNumber,
|
|
||||||
fingerprintUsername: currentFingerprint.username,
|
|
||||||
fingerprintHostname: currentFingerprint.hostname
|
|
||||||
})
|
})
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
||||||
@@ -208,7 +196,7 @@ export async function listClients(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { page, pageSize, query, filter } = parsedQuery.data;
|
const { page, pageSize, query } = parsedQuery.data;
|
||||||
|
|
||||||
const parsedParams = listClientsParamsSchema.safeParse(req.params);
|
const parsedParams = listClientsParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
@@ -262,15 +250,10 @@ export async function listClients(
|
|||||||
// Get client count with filter
|
// Get client count with filter
|
||||||
const conditions = [
|
const conditions = [
|
||||||
inArray(clients.clientId, accessibleClientIds),
|
inArray(clients.clientId, accessibleClientIds),
|
||||||
eq(clients.orgId, orgId)
|
eq(clients.orgId, orgId),
|
||||||
|
isNull(clients.userId)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (filter === "user") {
|
|
||||||
conditions.push(isNotNull(clients.userId));
|
|
||||||
} else if (filter === "machine") {
|
|
||||||
conditions.push(isNull(clients.userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
const countQuery = db.$count(
|
const countQuery = db.$count(
|
||||||
queryClientsBase().where(and(...conditions))
|
queryClientsBase().where(and(...conditions))
|
||||||
);
|
);
|
||||||
@@ -312,11 +295,8 @@ export async function listClients(
|
|||||||
|
|
||||||
// Merge clients with their site associations and replace name with device name
|
// Merge clients with their site associations and replace name with device name
|
||||||
const clientsWithSites = clientsList.map((client) => {
|
const clientsWithSites = clientsList.map((client) => {
|
||||||
const model = client.deviceModel || null;
|
|
||||||
const newName = getUserDeviceName(model, client.name);
|
|
||||||
return {
|
return {
|
||||||
...client,
|
...client,
|
||||||
name: newName,
|
|
||||||
sites: sitesByClient[client.clientId] || []
|
sites: sitesByClient[client.clientId] || []
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
302
server/routers/client/listUserDevices.ts
Normal file
302
server/routers/client/listUserDevices.ts
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
import {
|
||||||
|
clients,
|
||||||
|
currentFingerprint,
|
||||||
|
db,
|
||||||
|
olms,
|
||||||
|
orgs,
|
||||||
|
roleClients,
|
||||||
|
userClients,
|
||||||
|
users
|
||||||
|
} from "@server/db";
|
||||||
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import type { PaginatedResponse } from "@server/types/Pagination";
|
||||||
|
import { and, eq, inArray, isNotNull, or, sql } from "drizzle-orm";
|
||||||
|
import { NextFunction, Request, Response } from "express";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import NodeCache from "node-cache";
|
||||||
|
import semver from "semver";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tags = await response.json();
|
||||||
|
if (!Array.isArray(tags) || tags.length === 0) {
|
||||||
|
logger.warn("No tags found for Olm repository");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
tags = tags.filter((version) => !version.name.includes("rc"));
|
||||||
|
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 listUserDevicesParamsSchema = z.strictObject({
|
||||||
|
orgId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
const listUserDevicesSchema = z.object({
|
||||||
|
pageSize: z.coerce
|
||||||
|
.number<string>() // for prettier formatting
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.optional()
|
||||||
|
.catch(20)
|
||||||
|
.default(20),
|
||||||
|
page: z.coerce
|
||||||
|
.number<string>() // for prettier formatting
|
||||||
|
.int()
|
||||||
|
.min(0)
|
||||||
|
.optional()
|
||||||
|
.catch(1)
|
||||||
|
.default(1),
|
||||||
|
query: z.string().optional(),
|
||||||
|
sort_by: z.enum(["megabytesIn", "megabytesOut"]).optional().catch(undefined)
|
||||||
|
});
|
||||||
|
|
||||||
|
function queryUserDevicesBase() {
|
||||||
|
return db
|
||||||
|
.select({
|
||||||
|
clientId: clients.clientId,
|
||||||
|
orgId: clients.orgId,
|
||||||
|
name: clients.name,
|
||||||
|
pubKey: clients.pubKey,
|
||||||
|
subnet: clients.subnet,
|
||||||
|
megabytesIn: clients.megabytesIn,
|
||||||
|
megabytesOut: clients.megabytesOut,
|
||||||
|
orgName: orgs.name,
|
||||||
|
type: clients.type,
|
||||||
|
online: clients.online,
|
||||||
|
olmVersion: olms.version,
|
||||||
|
userId: clients.userId,
|
||||||
|
username: users.username,
|
||||||
|
userEmail: users.email,
|
||||||
|
niceId: clients.niceId,
|
||||||
|
agent: olms.agent,
|
||||||
|
approvalState: clients.approvalState,
|
||||||
|
olmArchived: olms.archived,
|
||||||
|
archived: clients.archived,
|
||||||
|
blocked: clients.blocked,
|
||||||
|
deviceModel: currentFingerprint.deviceModel,
|
||||||
|
fingerprintPlatform: currentFingerprint.platform,
|
||||||
|
fingerprintOsVersion: currentFingerprint.osVersion,
|
||||||
|
fingerprintKernelVersion: currentFingerprint.kernelVersion,
|
||||||
|
fingerprintArch: currentFingerprint.arch,
|
||||||
|
fingerprintSerialNumber: currentFingerprint.serialNumber,
|
||||||
|
fingerprintUsername: currentFingerprint.username,
|
||||||
|
fingerprintHostname: currentFingerprint.hostname
|
||||||
|
})
|
||||||
|
.from(clients)
|
||||||
|
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
||||||
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
|
.leftJoin(users, eq(clients.userId, users.userId))
|
||||||
|
.leftJoin(currentFingerprint, eq(olms.olmId, currentFingerprint.olmId));
|
||||||
|
}
|
||||||
|
|
||||||
|
type OlmWithUpdateAvailable = Awaited<
|
||||||
|
ReturnType<typeof queryUserDevicesBase>
|
||||||
|
>[0] & {
|
||||||
|
olmUpdateAvailable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListUserDevicesResponse = PaginatedResponse<{
|
||||||
|
devices: Array<OlmWithUpdateAvailable>;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "get",
|
||||||
|
path: "/org/{orgId}/user-devices",
|
||||||
|
description: "List all user devices for an organization.",
|
||||||
|
tags: [OpenAPITags.Client, OpenAPITags.Org],
|
||||||
|
request: {
|
||||||
|
query: listUserDevicesSchema,
|
||||||
|
params: listUserDevicesParamsSchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function listUserDevices(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedQuery = listUserDevicesSchema.safeParse(req.query);
|
||||||
|
if (!parsedQuery.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedQuery.error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { page, pageSize, query } = parsedQuery.data;
|
||||||
|
|
||||||
|
const parsedParams = listUserDevicesParamsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
|
if (req.user && orgId && orgId !== req.userOrgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.FORBIDDEN,
|
||||||
|
"User does not have access to this organization"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let accessibleClients;
|
||||||
|
if (req.user) {
|
||||||
|
accessibleClients = await db
|
||||||
|
.select({
|
||||||
|
clientId: sql<number>`COALESCE(${userClients.clientId}, ${roleClients.clientId})`
|
||||||
|
})
|
||||||
|
.from(userClients)
|
||||||
|
.fullJoin(
|
||||||
|
roleClients,
|
||||||
|
eq(userClients.clientId, roleClients.clientId)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
or(
|
||||||
|
eq(userClients.userId, req.user!.userId),
|
||||||
|
eq(roleClients.roleId, req.userOrgRoleId!)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
accessibleClients = await db
|
||||||
|
.select({ clientId: clients.clientId })
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.orgId, orgId));
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessibleClientIds = accessibleClients.map(
|
||||||
|
(client) => client.clientId
|
||||||
|
);
|
||||||
|
// Get client count with filter
|
||||||
|
const conditions = [
|
||||||
|
inArray(clients.clientId, accessibleClientIds),
|
||||||
|
eq(clients.orgId, orgId),
|
||||||
|
isNotNull(clients.userId)
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseQuery = queryUserDevicesBase().where(and(...conditions));
|
||||||
|
|
||||||
|
const countQuery = db.$count(baseQuery.as("filtered_clients"));
|
||||||
|
|
||||||
|
const [clientsList, totalCount] = await Promise.all([
|
||||||
|
baseQuery.limit(pageSize).offset(pageSize * (page - 1)),
|
||||||
|
countQuery
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Merge clients with their site associations and replace name with device name
|
||||||
|
const olmsWithUpdates: OlmWithUpdateAvailable[] = clientsList.map(
|
||||||
|
(client) => {
|
||||||
|
const model = client.deviceModel || null;
|
||||||
|
const newName = getUserDeviceName(model, client.name);
|
||||||
|
const OlmWithUpdate: OlmWithUpdateAvailable = {
|
||||||
|
...client,
|
||||||
|
name: newName
|
||||||
|
};
|
||||||
|
// 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 latestOlmVersion = await getLatestOlmVersion();
|
||||||
|
|
||||||
|
if (latestOlmVersion) {
|
||||||
|
olmsWithUpdates.forEach((client) => {
|
||||||
|
try {
|
||||||
|
client.olmUpdateAvailable = semver.lt(
|
||||||
|
client.olmVersion ? client.olmVersion : "",
|
||||||
|
latestOlmVersion
|
||||||
|
);
|
||||||
|
} 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<ListUserDevicesResponse>(res, {
|
||||||
|
data: {
|
||||||
|
devices: olmsWithUpdates,
|
||||||
|
pagination: {
|
||||||
|
total: totalCount,
|
||||||
|
page,
|
||||||
|
pageSize
|
||||||
|
}
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Clients retrieved successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -143,6 +143,13 @@ authenticated.get(
|
|||||||
client.listClients
|
client.listClients
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/user-devices",
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.listClients),
|
||||||
|
client.listUserDevices
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/client/:clientId",
|
"/client/:clientId",
|
||||||
verifyClientAccess,
|
verifyClientAccess,
|
||||||
|
|||||||
@@ -818,6 +818,13 @@ authenticated.get(
|
|||||||
client.listClients
|
client.listClients
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/user-devices",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.listClients),
|
||||||
|
client.listUserDevices
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/client/:clientId",
|
"/client/:clientId",
|
||||||
verifyApiKeyClientAccess,
|
verifyApiKeyClientAccess,
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ export async function listResources(
|
|||||||
hcEnabled: targetHealthCheck.hcEnabled
|
hcEnabled: targetHealthCheck.hcEnabled
|
||||||
})
|
})
|
||||||
.from(targets)
|
.from(targets)
|
||||||
.where(sql`${targets.resourceId} in ${resourceIdList}`)
|
.where(inArray(targets.resourceId, resourceIdList))
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
targetHealthCheck,
|
targetHealthCheck,
|
||||||
eq(targetHealthCheck.targetId, targets.targetId)
|
eq(targetHealthCheck.targetId, targets.targetId)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { internal } from "@app/lib/api";
|
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
|
||||||
import { AxiosResponse } from "axios";
|
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { ListClientsResponse } from "@server/routers/client";
|
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
import type { ClientRow } from "@app/components/UserDevicesTable";
|
import type { ClientRow } from "@app/components/UserDevicesTable";
|
||||||
import UserDevicesTable from "@app/components/UserDevicesTable";
|
import UserDevicesTable from "@app/components/UserDevicesTable";
|
||||||
|
import { internal } from "@app/lib/api";
|
||||||
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
import { type ListUserDevicesResponse } from "@server/routers/client";
|
||||||
|
import type { Pagination } from "@server/types/Pagination";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
type ClientsPageProps = {
|
type ClientsPageProps = {
|
||||||
params: Promise<{ orgId: string }>;
|
params: Promise<{ orgId: string }>;
|
||||||
@@ -18,14 +19,21 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
|
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
|
|
||||||
let userClients: ListClientsResponse["clients"] = [];
|
let userClients: ListUserDevicesResponse["devices"] = [];
|
||||||
|
|
||||||
|
let pagination: Pagination = {
|
||||||
|
page: 1,
|
||||||
|
total: 0,
|
||||||
|
pageSize: 20
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userRes = await internal.get<AxiosResponse<ListClientsResponse>>(
|
const userRes = await internal.get<
|
||||||
`/org/${params.orgId}/clients?filter=user`,
|
AxiosResponse<ListUserDevicesResponse>
|
||||||
await authCookieHeader()
|
>(`/org/${params.orgId}/user-devices`, await authCookieHeader());
|
||||||
);
|
const responseData = userRes.data.data;
|
||||||
userClients = userRes.data.data.clients;
|
userClients = responseData.devices;
|
||||||
|
pagination = responseData.pagination;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
||||||
function formatSize(mb: number): string {
|
function formatSize(mb: number): string {
|
||||||
@@ -39,31 +47,29 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mapClientToRow = (
|
const mapClientToRow = (
|
||||||
client: ListClientsResponse["clients"][0]
|
client: ListUserDevicesResponse["devices"][number]
|
||||||
): ClientRow => {
|
): ClientRow => {
|
||||||
// Build fingerprint object if any fingerprint data exists
|
// Build fingerprint object if any fingerprint data exists
|
||||||
const hasFingerprintData =
|
const hasFingerprintData =
|
||||||
(client as any).fingerprintPlatform ||
|
client.fingerprintPlatform ||
|
||||||
(client as any).fingerprintOsVersion ||
|
client.fingerprintOsVersion ||
|
||||||
(client as any).fingerprintKernelVersion ||
|
client.fingerprintKernelVersion ||
|
||||||
(client as any).fingerprintArch ||
|
client.fingerprintArch ||
|
||||||
(client as any).fingerprintSerialNumber ||
|
client.fingerprintSerialNumber ||
|
||||||
(client as any).fingerprintUsername ||
|
client.fingerprintUsername ||
|
||||||
(client as any).fingerprintHostname ||
|
client.fingerprintHostname ||
|
||||||
(client as any).deviceModel;
|
client.deviceModel;
|
||||||
|
|
||||||
const fingerprint = hasFingerprintData
|
const fingerprint = hasFingerprintData
|
||||||
? {
|
? {
|
||||||
platform: (client as any).fingerprintPlatform || null,
|
platform: client.fingerprintPlatform,
|
||||||
osVersion: (client as any).fingerprintOsVersion || null,
|
osVersion: client.fingerprintOsVersion,
|
||||||
kernelVersion:
|
kernelVersion: client.fingerprintKernelVersion,
|
||||||
(client as any).fingerprintKernelVersion || null,
|
arch: client.fingerprintArch,
|
||||||
arch: (client as any).fingerprintArch || null,
|
deviceModel: client.deviceModel,
|
||||||
deviceModel: (client as any).deviceModel || null,
|
serialNumber: client.fingerprintSerialNumber,
|
||||||
serialNumber:
|
username: client.fingerprintUsername,
|
||||||
(client as any).fingerprintSerialNumber || null,
|
hostname: client.fingerprintHostname
|
||||||
username: (client as any).fingerprintUsername || null,
|
|
||||||
hostname: (client as any).fingerprintHostname || null
|
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -71,19 +77,19 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
name: client.name,
|
name: client.name,
|
||||||
id: client.clientId,
|
id: client.clientId,
|
||||||
subnet: client.subnet.split("/")[0],
|
subnet: client.subnet.split("/")[0],
|
||||||
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,
|
olmVersion: client.olmVersion || undefined,
|
||||||
olmUpdateAvailable: client.olmUpdateAvailable || false,
|
olmUpdateAvailable: Boolean(client.olmUpdateAvailable),
|
||||||
userId: client.userId,
|
userId: client.userId,
|
||||||
username: client.username,
|
username: client.username,
|
||||||
userEmail: client.userEmail,
|
userEmail: client.userEmail,
|
||||||
niceId: client.niceId,
|
niceId: client.niceId,
|
||||||
agent: client.agent,
|
agent: client.agent,
|
||||||
archived: client.archived || false,
|
archived: Boolean(client.archived),
|
||||||
blocked: client.blocked || false,
|
blocked: Boolean(client.blocked),
|
||||||
approvalState: client.approvalState,
|
approvalState: client.approvalState,
|
||||||
fingerprint
|
fingerprint
|
||||||
};
|
};
|
||||||
@@ -91,6 +97,11 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
|
|
||||||
const userClientRows: ClientRow[] = userClients.map(mapClientToRow);
|
const userClientRows: ClientRow[] = userClients.map(mapClientToRow);
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
userClientRows,
|
||||||
|
pagination
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsSectionTitle
|
<SettingsSectionTitle
|
||||||
|
|||||||
@@ -255,10 +255,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
const { data: usersResponse = [] } = useQuery(orgQueries.users({ orgId }));
|
const { data: usersResponse = [] } = useQuery(orgQueries.users({ orgId }));
|
||||||
const { data: clientsResponse = [] } = useQuery(
|
const { data: clientsResponse = [] } = useQuery(
|
||||||
orgQueries.clients({
|
orgQueries.clients({
|
||||||
orgId,
|
orgId
|
||||||
filters: {
|
|
||||||
filter: "machine"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -277,10 +277,7 @@ export default function EditInternalResourceDialog({
|
|||||||
orgQueries.roles({ orgId }),
|
orgQueries.roles({ orgId }),
|
||||||
orgQueries.users({ orgId }),
|
orgQueries.users({ orgId }),
|
||||||
orgQueries.clients({
|
orgQueries.clients({
|
||||||
orgId,
|
orgId
|
||||||
filters: {
|
|
||||||
filter: "machine"
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
resourceQueries.siteResourceUsers({ siteResourceId: resource.id }),
|
resourceQueries.siteResourceUsers({ siteResourceId: resource.id }),
|
||||||
resourceQueries.siteResourceRoles({ siteResourceId: resource.id }),
|
resourceQueries.siteResourceRoles({ siteResourceId: resource.id }),
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ export const productUpdatesQueries = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const clientFilterSchema = z.object({
|
export const clientFilterSchema = z.object({
|
||||||
filter: z.enum(["machine", "user"]),
|
pageSize: z.int().prefault(1000).optional()
|
||||||
limit: z.int().prefault(1000).optional()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const orgQueries = {
|
export const orgQueries = {
|
||||||
@@ -96,14 +95,13 @@ export const orgQueries = {
|
|||||||
filters
|
filters
|
||||||
}: {
|
}: {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
filters: z.infer<typeof clientFilterSchema>;
|
filters?: z.infer<typeof clientFilterSchema>;
|
||||||
}) =>
|
}) =>
|
||||||
queryOptions({
|
queryOptions({
|
||||||
queryKey: ["ORG", orgId, "CLIENTS", filters] as const,
|
queryKey: ["ORG", orgId, "CLIENTS", filters] as const,
|
||||||
queryFn: async ({ signal, meta }) => {
|
queryFn: async ({ signal, meta }) => {
|
||||||
const sp = new URLSearchParams({
|
const sp = new URLSearchParams({
|
||||||
...filters,
|
pageSize: (filters?.pageSize ?? 1000).toString()
|
||||||
limit: (filters.limit ?? 1000).toString()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await meta!.api.get<
|
const res = await meta!.api.get<
|
||||||
|
|||||||
Reference in New Issue
Block a user