mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-31 15:06:42 +00:00
Fix access log
This commit is contained in:
@@ -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,17 +137,22 @@ 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 }>();
|
||||||
|
|
||||||
|
if (resourceIds.length > 0) {
|
||||||
const resourceDetails = await primaryDb
|
const resourceDetails = await primaryDb
|
||||||
.select({
|
.select({
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
@@ -156,17 +162,48 @@ async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAc
|
|||||||
.from(resources)
|
.from(resources)
|
||||||
.where(inArray(resources.resourceId, resourceIds));
|
.where(inArray(resources.resourceId, resourceIds));
|
||||||
|
|
||||||
// Create a map for quick lookup
|
for (const r of resourceDetails) {
|
||||||
const resourceMap = new Map(
|
resourceMap.set(r.resourceId, { name: r.name, niceId: r.niceId });
|
||||||
resourceDetails.map(r => [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 => {
|
||||||
|
if (log.resourceId != null) {
|
||||||
|
const details = resourceMap.get(log.resourceId);
|
||||||
|
return {
|
||||||
...log,
|
...log,
|
||||||
resourceName: log.resourceId ? resourceMap.get(log.resourceId)?.name ?? null : null,
|
resourceName: details?.name ?? null,
|
||||||
resourceNiceId: log.resourceId ? resourceMap.get(log.resourceId)?.niceId ?? null : 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 = [
|
||||||
|
...resourcesWithNames,
|
||||||
|
...resourceDetails.map(r => ({
|
||||||
id: r.resourceId,
|
id: r.resourceId,
|
||||||
name: r.name
|
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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user