fix minifying and reuse code
This commit is contained in:
@@ -1076,6 +1076,23 @@ export function listClients(filters: ListFilters): ListResult {
|
||||
|
||||
const clientFields =
|
||||
`c.id, c.hwid, c.role, c.ip, c.host, c.os, c.arch, c.version, c.user, c.nickname, c.custom_tag as customTag, c.custom_tag_note as customTagNote, c.monitors, c.country, c.last_seen as lastSeen, c.online, c.ping_ms as pingMs, c.bookmarked, c.build_tag as buildTag, c.built_by_user_id as builtByUserId, c.enrollment_status as enrollmentStatus, c.public_key as publicKey, c.key_fingerprint as keyFingerprint, c.cpu, c.gpu, c.ram, c.battery_percent as batteryPercent, c.battery_charging as batteryCharging, CASE WHEN ${webcamAvailableSql} THEN 1 ELSE 0 END as webcamAvailable, c.webcam_devices as webcamDevices, c.is_admin as isAdmin, c.elevation, c.permissions, c.disconnect_reason as disconnectReason, c.disconnect_detail as disconnectDetail, c.group_id as groupId, c.notifications_muted as notificationsMuted, c.deny_reason as denyReason`;
|
||||
const searchFields =
|
||||
`c.id, c.hwid, c.ip, c.host, c.os, c.arch, c.version, c.user, c.nickname, c.custom_tag as customTag, c.custom_tag_note as customTagNote, c.monitors, c.country, c.online, c.build_tag as buildTag, c.cpu, c.gpu, c.ram, g.name as groupName`;
|
||||
|
||||
const fetchClientRowsByIds = (ids: string[]): any[] => {
|
||||
if (!ids.length) return [];
|
||||
const placeholders = ids.map(() => "?").join(",");
|
||||
const fullRows = db
|
||||
.query<any>(
|
||||
`SELECT ${clientFields}, g.name as groupName, g.color as groupColor
|
||||
FROM clients c
|
||||
LEFT JOIN client_groups g ON g.id = c.group_id
|
||||
WHERE c.id IN (${placeholders})`,
|
||||
)
|
||||
.all(...ids);
|
||||
const byId = new Map(fullRows.map((row: any) => [row.id, row]));
|
||||
return ids.map((id) => byId.get(id)).filter(Boolean);
|
||||
};
|
||||
|
||||
let rows: any[] | undefined;
|
||||
let totalCount = 0;
|
||||
@@ -1101,6 +1118,10 @@ export function listClients(filters: ListFilters): ListResult {
|
||||
if (searchCandidate.sql) {
|
||||
searchWhere.push(searchCandidate.sql);
|
||||
searchParams.push(...searchCandidate.params);
|
||||
} else {
|
||||
rows = [];
|
||||
totalCount = 0;
|
||||
onlineCount = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1108,7 +1129,7 @@ export function listClients(filters: ListFilters): ListResult {
|
||||
const searchWhereSql = searchWhere.length ? `WHERE ${searchWhere.join(" AND ")}` : "";
|
||||
const candidates = db
|
||||
.query<any>(
|
||||
`SELECT ${clientFields}, g.name as groupName, g.color as groupColor
|
||||
`SELECT ${searchFields}
|
||||
FROM clients c
|
||||
LEFT JOIN client_groups g ON g.id = c.group_id
|
||||
${searchWhereSql}
|
||||
@@ -1118,7 +1139,7 @@ export function listClients(filters: ListFilters): ListResult {
|
||||
const matches = fuzzySearchClientRows(candidates, search);
|
||||
totalCount = matches.length;
|
||||
onlineCount = matches.filter((row: any) => row.online === 1).length;
|
||||
rows = matches.slice(offset, offset + pageSize);
|
||||
rows = fetchClientRowsByIds(matches.slice(offset, offset + pageSize).map((row: any) => row.id));
|
||||
}
|
||||
} else {
|
||||
const countJoinSql = needsGroupJoinForFilter ? "LEFT JOIN client_groups g ON g.id = c.group_id" : "";
|
||||
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
declare module "qrcode" {
|
||||
type QrOptions = {
|
||||
errorCorrectionLevel?: string;
|
||||
margin?: number;
|
||||
};
|
||||
|
||||
type QrCode = {
|
||||
modules: {
|
||||
size: number;
|
||||
get(col: number, row: number): boolean;
|
||||
};
|
||||
};
|
||||
|
||||
const QRCode: {
|
||||
create(text: string, options?: QrOptions): QrCode;
|
||||
};
|
||||
|
||||
export default QRCode;
|
||||
}
|
||||
@@ -95,6 +95,7 @@ function sentCommandTypes(sent: Uint8Array[]): string[] {
|
||||
async function resolvePendingOnce(pendingCommandReplies: Map<string, any>) {
|
||||
const first = pendingCommandReplies.entries().next().value;
|
||||
expect(first).toBeTruthy();
|
||||
if (!first) throw new Error("Expected one pending command reply");
|
||||
const [cmdId, pending] = first;
|
||||
clearTimeout(pending.timeout);
|
||||
pending.resolve({ ok: true, message: "ok" });
|
||||
|
||||
@@ -572,7 +572,7 @@ export async function compilePluginTypeScript(entryPath: string, outFile: string
|
||||
minify: false,
|
||||
sourcemap: "none",
|
||||
write: false,
|
||||
});
|
||||
} as Parameters<typeof Bun.build>[0]);
|
||||
if (!result.success) {
|
||||
const logs = result.logs.map((log) => log.message).join("; ");
|
||||
throw new Error(`TypeScript build failed for ${pluginId}: ${logs || "unknown error"}`);
|
||||
@@ -605,7 +605,9 @@ export function normalizePluginNeeds(raw: any): PluginNeeds | undefined {
|
||||
const bucket = typeof item.bucket === "string" ? item.bucket.trim() : "";
|
||||
if (!FILE_BUCKETS.has(bucket)) continue;
|
||||
const accessRaw = Array.isArray(item.access) ? item.access : [];
|
||||
const access = Array.from(new Set(accessRaw.filter((a: any) => typeof a === "string" && FILE_ACCESS.has(a))));
|
||||
const access = Array.from(
|
||||
new Set<string>(accessRaw.filter((a: any): a is string => typeof a === "string" && FILE_ACCESS.has(a))),
|
||||
);
|
||||
if (access.length === 0) continue;
|
||||
files.push({
|
||||
bucket,
|
||||
|
||||
@@ -23,6 +23,16 @@ function acceptsGzip(req: Request): boolean {
|
||||
const STATIC_ASSET_CACHE = "public, max-age=31536000, immutable";
|
||||
const MUTABLE_ASSET_CACHE = "public, max-age=86400, stale-while-revalidate=604800";
|
||||
const NO_CACHE = "no-cache";
|
||||
const MAX_GZIP_CACHE_ENTRIES = 128;
|
||||
const MAX_GZIP_CACHE_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
type GzipCacheEntry = {
|
||||
bytes: ArrayBuffer;
|
||||
size: number;
|
||||
};
|
||||
|
||||
const gzipAssetCache = new Map<string, GzipCacheEntry>();
|
||||
let gzipAssetCacheBytes = 0;
|
||||
|
||||
function assetCacheControl(relativePath: string): string {
|
||||
if (relativePath === "custom.css") return NO_CACHE;
|
||||
@@ -36,16 +46,56 @@ function assetCacheControl(relativePath: string): string {
|
||||
return MUTABLE_ASSET_CACHE;
|
||||
}
|
||||
|
||||
function toExactArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
||||
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
||||
}
|
||||
|
||||
function rememberCompressedAsset(cacheKey: string, bytes: Uint8Array): ArrayBuffer {
|
||||
const existing = gzipAssetCache.get(cacheKey);
|
||||
if (existing) {
|
||||
gzipAssetCacheBytes -= existing.size;
|
||||
}
|
||||
|
||||
const cachedBytes = toExactArrayBuffer(bytes);
|
||||
gzipAssetCache.set(cacheKey, { bytes: cachedBytes, size: bytes.byteLength });
|
||||
gzipAssetCacheBytes += bytes.byteLength;
|
||||
|
||||
while (
|
||||
gzipAssetCache.size > MAX_GZIP_CACHE_ENTRIES ||
|
||||
gzipAssetCacheBytes > MAX_GZIP_CACHE_BYTES
|
||||
) {
|
||||
const oldestKey = gzipAssetCache.keys().next().value;
|
||||
if (!oldestKey) break;
|
||||
const oldest = gzipAssetCache.get(oldestKey);
|
||||
if (oldest) gzipAssetCacheBytes -= oldest.size;
|
||||
gzipAssetCache.delete(oldestKey);
|
||||
}
|
||||
|
||||
return cachedBytes;
|
||||
}
|
||||
|
||||
async function compressedResponse(
|
||||
req: Request,
|
||||
body: Uint8Array | ArrayBuffer,
|
||||
cacheKey: string,
|
||||
file: ReturnType<typeof Bun.file>,
|
||||
headers: Record<string, string>,
|
||||
): Promise<Response> {
|
||||
if (!isCompressible(headers["Content-Type"] ?? "") || !acceptsGzip(req) || body.byteLength < 1024) {
|
||||
return new Response(body as BodyInit, { headers });
|
||||
const cached = gzipAssetCache.get(cacheKey);
|
||||
if (cached) {
|
||||
gzipAssetCache.delete(cacheKey);
|
||||
gzipAssetCache.set(cacheKey, cached);
|
||||
return new Response(cached.bytes, {
|
||||
headers: { ...headers, "Content-Encoding": "gzip", "Vary": "Accept-Encoding" },
|
||||
});
|
||||
}
|
||||
const compressed = Bun.gzipSync(new Uint8Array(body instanceof ArrayBuffer ? body : body));
|
||||
return new Response(compressed, {
|
||||
|
||||
const body = await file.arrayBuffer();
|
||||
if (body.byteLength < 1024) {
|
||||
return new Response(body, { headers });
|
||||
}
|
||||
|
||||
const compressed = Bun.gzipSync(new Uint8Array(body));
|
||||
const compressedBody = rememberCompressedAsset(cacheKey, compressed);
|
||||
return new Response(compressedBody, {
|
||||
headers: { ...headers, "Content-Encoding": "gzip", "Vary": "Accept-Encoding" },
|
||||
});
|
||||
}
|
||||
@@ -121,8 +171,7 @@ export async function handleAssetsRoutes(
|
||||
}
|
||||
|
||||
if (isCompressible(contentType) && acceptsGzip(req)) {
|
||||
const bytes = await file.arrayBuffer();
|
||||
return compressedResponse(req, bytes, headers);
|
||||
return compressedResponse(`${resolvedPath}:${etag}`, file, headers);
|
||||
}
|
||||
return new Response(file, { headers });
|
||||
}
|
||||
|
||||
@@ -261,8 +261,8 @@ function pickSteppedNumber(
|
||||
return Math.round(clamped / step) * step;
|
||||
}
|
||||
|
||||
function assignIfDefined<T extends Record<string, unknown>>(
|
||||
target: T,
|
||||
function assignIfDefined(
|
||||
target: Record<string, unknown>,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void {
|
||||
|
||||
@@ -46,6 +46,26 @@ const latestFrames = new Map<string, { bytes: Uint8Array; format: string; captur
|
||||
const thumbnailRequests = new Map<string, number>();
|
||||
const thumbnailVersionHWM = new Map<string, number>();
|
||||
|
||||
type BunImageConstructor = new (
|
||||
bytes: Uint8Array,
|
||||
options?: { autoOrient?: boolean; maxPixels?: number },
|
||||
) => {
|
||||
resize(
|
||||
width: number,
|
||||
height: number,
|
||||
options?: { fit?: "inside"; withoutEnlargement?: boolean },
|
||||
): {
|
||||
webp(options?: { quality?: number }): {
|
||||
bytes(): Promise<ArrayBuffer | Uint8Array>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
function getBunImage(): BunImageConstructor | null {
|
||||
const imageCtor = (Bun as unknown as { Image?: BunImageConstructor }).Image;
|
||||
return typeof imageCtor === "function" ? imageCtor : null;
|
||||
}
|
||||
|
||||
function touchThumbnailLRU(id: string) {
|
||||
const existing = thumbnails.get(id);
|
||||
if (!existing) return;
|
||||
@@ -117,11 +137,12 @@ async function buildThumbnailBytes(bytes: Uint8Array, format: string): Promise<U
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof Bun.Image !== "function") {
|
||||
const BunImage = getBunImage();
|
||||
if (!BunImage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const output = await new Bun.Image(bytes, {
|
||||
const output = await new BunImage(bytes, {
|
||||
autoOrient: true,
|
||||
maxPixels: THUMBNAIL_WIDTH * THUMBNAIL_HEIGHT * 16,
|
||||
})
|
||||
@@ -164,7 +185,7 @@ export async function generateThumbnail(id: string): Promise<boolean> {
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
if (typeof Bun.Image === "function") {
|
||||
if (getBunImage()) {
|
||||
console.error(`[thumbnails] Failed to generate thumbnail for client ${id}:`, err);
|
||||
}
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user