Fix access log

This commit is contained in:
Owen
2026-03-30 11:58:48 -07:00
parent e0c96e7224
commit 04943fb4a6
2 changed files with 102 additions and 28 deletions

View File

@@ -11,11 +11,11 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import { accessAuditLog, logsDb, resources, db, primaryDb } from "@server/db"; import { accessAuditLog, logsDb, resources, siteResources, db, primaryDb } from "@server/db";
import { registry } from "@server/openApi"; import { registry } from "@server/openApi";
import { NextFunction } from "express"; import { NextFunction } from "express";
import { Request, Response } from "express"; import { Request, Response } from "express";
import { eq, gt, lt, and, count, desc, inArray } from "drizzle-orm"; import { eq, gt, lt, and, count, desc, inArray, isNull } from "drizzle-orm";
import { OpenAPITags } from "@server/openApi"; import { OpenAPITags } from "@server/openApi";
import { z } from "zod"; import { z } from "zod";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -122,6 +122,7 @@ export function queryAccess(data: Q) {
actorType: accessAuditLog.actorType, actorType: accessAuditLog.actorType,
actorId: accessAuditLog.actorId, actorId: accessAuditLog.actorId,
resourceId: accessAuditLog.resourceId, resourceId: accessAuditLog.resourceId,
siteResourceId: accessAuditLog.siteResourceId,
ip: accessAuditLog.ip, ip: accessAuditLog.ip,
location: accessAuditLog.location, location: accessAuditLog.location,
userAgent: accessAuditLog.userAgent, userAgent: accessAuditLog.userAgent,
@@ -136,37 +137,73 @@ export function queryAccess(data: Q) {
} }
async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAccess>>) { async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAccess>>) {
// If logs database is the same as main database, we can do a join
// Otherwise, we need to fetch resource details separately
const resourceIds = logs const resourceIds = logs
.map(log => log.resourceId) .map(log => log.resourceId)
.filter((id): id is number => id !== null && id !== undefined); .filter((id): id is number => id !== null && id !== undefined);
if (resourceIds.length === 0) { const siteResourceIds = logs
.filter(log => log.resourceId == null && log.siteResourceId != null)
.map(log => log.siteResourceId)
.filter((id): id is number => id !== null && id !== undefined);
if (resourceIds.length === 0 && siteResourceIds.length === 0) {
return logs.map(log => ({ ...log, resourceName: null, resourceNiceId: null })); return logs.map(log => ({ ...log, resourceName: null, resourceNiceId: null }));
} }
// Fetch resource details from main database const resourceMap = new Map<number, { name: string | null; niceId: string | null }>();
const resourceDetails = await primaryDb
.select({
resourceId: resources.resourceId,
name: resources.name,
niceId: resources.niceId
})
.from(resources)
.where(inArray(resources.resourceId, resourceIds));
// Create a map for quick lookup if (resourceIds.length > 0) {
const resourceMap = new Map( const resourceDetails = await primaryDb
resourceDetails.map(r => [r.resourceId, { name: r.name, niceId: r.niceId }]) .select({
); resourceId: resources.resourceId,
name: resources.name,
niceId: resources.niceId
})
.from(resources)
.where(inArray(resources.resourceId, resourceIds));
for (const r of resourceDetails) {
resourceMap.set(r.resourceId, { name: r.name, niceId: r.niceId });
}
}
const siteResourceMap = new Map<number, { name: string | null; niceId: string | null }>();
if (siteResourceIds.length > 0) {
const siteResourceDetails = await primaryDb
.select({
siteResourceId: siteResources.siteResourceId,
name: siteResources.name,
niceId: siteResources.niceId
})
.from(siteResources)
.where(inArray(siteResources.siteResourceId, siteResourceIds));
for (const r of siteResourceDetails) {
siteResourceMap.set(r.siteResourceId, { name: r.name, niceId: r.niceId });
}
}
// Enrich logs with resource details // Enrich logs with resource details
return logs.map(log => ({ return logs.map(log => {
...log, if (log.resourceId != null) {
resourceName: log.resourceId ? resourceMap.get(log.resourceId)?.name ?? null : null, const details = resourceMap.get(log.resourceId);
resourceNiceId: log.resourceId ? resourceMap.get(log.resourceId)?.niceId ?? null : null return {
})); ...log,
resourceName: details?.name ?? null,
resourceNiceId: details?.niceId ?? null
};
} else if (log.siteResourceId != null) {
const details = siteResourceMap.get(log.siteResourceId);
return {
...log,
resourceId: log.siteResourceId,
resourceName: details?.name ?? null,
resourceNiceId: details?.niceId ?? null
};
}
return { ...log, resourceName: null, resourceNiceId: null };
});
} }
export function countAccessQuery(data: Q) { export function countAccessQuery(data: Q) {
@@ -212,11 +249,23 @@ async function queryUniqueFilterAttributes(
.from(accessAuditLog) .from(accessAuditLog)
.where(baseConditions); .where(baseConditions);
// Get unique siteResources (only for logs where resourceId is null)
const uniqueSiteResources = await logsDb
.selectDistinct({
id: accessAuditLog.siteResourceId
})
.from(accessAuditLog)
.where(and(baseConditions, isNull(accessAuditLog.resourceId)));
// Fetch resource names from main database for the unique resource IDs // Fetch resource names from main database for the unique resource IDs
const resourceIds = uniqueResources const resourceIds = uniqueResources
.map(row => row.id) .map(row => row.id)
.filter((id): id is number => id !== null); .filter((id): id is number => id !== null);
const siteResourceIds = uniqueSiteResources
.map(row => row.id)
.filter((id): id is number => id !== null);
let resourcesWithNames: Array<{ id: number; name: string | null }> = []; let resourcesWithNames: Array<{ id: number; name: string | null }> = [];
if (resourceIds.length > 0) { if (resourceIds.length > 0) {
@@ -228,10 +277,31 @@ async function queryUniqueFilterAttributes(
.from(resources) .from(resources)
.where(inArray(resources.resourceId, resourceIds)); .where(inArray(resources.resourceId, resourceIds));
resourcesWithNames = resourceDetails.map(r => ({ resourcesWithNames = [
id: r.resourceId, ...resourcesWithNames,
name: r.name ...resourceDetails.map(r => ({
})); id: r.resourceId,
name: r.name
}))
];
}
if (siteResourceIds.length > 0) {
const siteResourceDetails = await primaryDb
.select({
siteResourceId: siteResources.siteResourceId,
name: siteResources.name
})
.from(siteResources)
.where(inArray(siteResources.siteResourceId, siteResourceIds));
resourcesWithNames = [
...resourcesWithNames,
...siteResourceDetails.map(r => ({
id: r.siteResourceId,
name: r.name
}))
];
} }
return { return {

View File

@@ -465,7 +465,11 @@ export default function GeneralPage() {
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<Link <Link
href={`/${row.original.orgId}/settings/resources/proxy/${row.original.resourceNiceId}`} href={
row.original.type === "ssh"
? `/${row.original.orgId}/settings/resources/client?query=${row.original.resourceNiceId}`
: `/${row.original.orgId}/settings/resources/proxy/${row.original.resourceNiceId}`
}
> >
<Button <Button
variant="outline" variant="outline"