mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-12 15:30:53 +02:00
show the sessions in the ui
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { registry } from "@server/openApi";
|
||||
import { NextFunction } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { OpenAPITags } from "@server/openApi";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { z } from "zod";
|
||||
import logger from "@server/logger";
|
||||
import {
|
||||
queryAiSessionLogsQuery,
|
||||
queryAiSessionLogsParams,
|
||||
queryAiSession,
|
||||
countAiSessionQuery
|
||||
} from "./queryAiSessionLog";
|
||||
import { generateCSV } from "./generateCSV";
|
||||
|
||||
const MAX_EXPORT_LIMIT = 50_000;
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/logs/ai/export",
|
||||
description: "Export the AI gateway session log for an organization as CSV",
|
||||
tags: [OpenAPITags.Logs],
|
||||
request: {
|
||||
query: queryAiSessionLogsQuery.omit({
|
||||
limit: true,
|
||||
offset: true
|
||||
}),
|
||||
params: queryAiSessionLogsParams
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.record(z.string(), z.any()).nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function exportAiSessionLogs(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedQuery = queryAiSessionLogsQuery.safeParse(req.query);
|
||||
if (!parsedQuery.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedQuery.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const parsedParams = queryAiSessionLogsParams.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = { ...parsedQuery.data, ...parsedParams.data };
|
||||
|
||||
const [{ count }] = await countAiSessionQuery(data);
|
||||
if (count > MAX_EXPORT_LIMIT) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
`Export limit exceeded. Your selection contains ${count} rows, but the maximum is ${MAX_EXPORT_LIMIT} rows. Please select a shorter time range to reduce the data.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const baseQuery = queryAiSession(data);
|
||||
|
||||
const log = await baseQuery.limit(MAX_EXPORT_LIMIT);
|
||||
|
||||
const csvData = generateCSV(log);
|
||||
|
||||
res.setHeader("Content-Type", "text/csv");
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="ai-session-logs-${data.orgId}-${Date.now()}.csv"`
|
||||
);
|
||||
|
||||
return res.send(csvData);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from "./queryRequestAuditLog";
|
||||
export * from "./queryRequestAnalytics";
|
||||
export * from "./exportRequestAuditLog";
|
||||
export * from "./queryAiSessionLog";
|
||||
export * from "./exportAiSessionLog";
|
||||
|
||||
@@ -0,0 +1,511 @@
|
||||
import {
|
||||
logsDb,
|
||||
aiSessionLog,
|
||||
aiProviders,
|
||||
resources,
|
||||
siteResources,
|
||||
users,
|
||||
db,
|
||||
primaryDb
|
||||
} from "@server/db";
|
||||
import { registry } from "@server/openApi";
|
||||
import { NextFunction } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import { eq, gt, lt, and, count, desc, inArray, isNull, or } from "drizzle-orm";
|
||||
import { OpenAPITags } from "@server/openApi";
|
||||
import { z } from "zod";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { QueryAiSessionLogResponse } from "@server/routers/auditLogs/types";
|
||||
import { AI_CAPABILITIES } from "@server/lib/aiCapabilities";
|
||||
import response from "@server/lib/response";
|
||||
import logger from "@server/logger";
|
||||
import { getSevenDaysAgo } from "@app/lib/getSevenDaysAgo";
|
||||
|
||||
export const queryAiSessionLogsQuery = z.strictObject({
|
||||
// iso string just validate its a parseable date
|
||||
timeStart: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
error: "timeStart must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => new Date(val).getTime())
|
||||
.prefault(() => getSevenDaysAgo().toISOString())
|
||||
.openapi({
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
description:
|
||||
"Start time as ISO date string (defaults to 7 days ago)"
|
||||
}),
|
||||
timeEnd: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
error: "timeEnd must be a valid ISO date string"
|
||||
})
|
||||
.transform((val) => new Date(val).getTime())
|
||||
.optional()
|
||||
.prefault(() => new Date().toISOString())
|
||||
.openapi({
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
description:
|
||||
"End time as ISO date string (defaults to current time)"
|
||||
}),
|
||||
providerId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.int().positive())
|
||||
.optional(),
|
||||
capability: z.enum(AI_CAPABILITIES).optional(),
|
||||
resourceId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.int().positive())
|
||||
.optional(),
|
||||
actor: z.string().optional(),
|
||||
isStream: z
|
||||
.union([z.boolean(), z.string()])
|
||||
.transform((val) => (typeof val === "string" ? val === "true" : val))
|
||||
.optional(),
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.transform(Number)
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("0")
|
||||
.transform(Number)
|
||||
.pipe(z.int().nonnegative())
|
||||
});
|
||||
|
||||
export const queryAiSessionLogsParams = z.object({
|
||||
orgId: z.string()
|
||||
});
|
||||
|
||||
export const queryAiSessionLogsCombined = queryAiSessionLogsQuery.merge(
|
||||
queryAiSessionLogsParams
|
||||
);
|
||||
type Q = z.infer<typeof queryAiSessionLogsCombined>;
|
||||
|
||||
function sortNamedFilterOptions<T extends { id: number; name: string | null }>(
|
||||
items: T[]
|
||||
): T[] {
|
||||
return [...items].sort((a, b) => {
|
||||
const nameA = a.name ?? "";
|
||||
const nameB = b.name ?? "";
|
||||
|
||||
if (nameA < nameB) return -1;
|
||||
if (nameA > nameB) return 1;
|
||||
|
||||
return a.id - b.id;
|
||||
});
|
||||
}
|
||||
|
||||
function getWhere(data: Q) {
|
||||
return and(
|
||||
gt(aiSessionLog.createdAt, data.timeStart),
|
||||
lt(aiSessionLog.createdAt, data.timeEnd),
|
||||
eq(aiSessionLog.orgId, data.orgId),
|
||||
data.providerId
|
||||
? eq(aiSessionLog.providerId, data.providerId)
|
||||
: undefined,
|
||||
data.capability ? eq(aiSessionLog.capability, data.capability) : undefined,
|
||||
data.resourceId
|
||||
? or(
|
||||
eq(aiSessionLog.resourceId, data.resourceId),
|
||||
eq(aiSessionLog.siteResourceId, data.resourceId)
|
||||
)
|
||||
: undefined,
|
||||
data.actor ? eq(aiSessionLog.userId, data.actor) : undefined,
|
||||
data.isStream !== undefined
|
||||
? eq(aiSessionLog.isStream, data.isStream)
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
|
||||
export function queryAiSession(data: Q) {
|
||||
return logsDb
|
||||
.select({
|
||||
id: aiSessionLog.id,
|
||||
sessionId: aiSessionLog.sessionId,
|
||||
orgId: aiSessionLog.orgId,
|
||||
providerId: aiSessionLog.providerId,
|
||||
capability: aiSessionLog.capability,
|
||||
resourceId: aiSessionLog.resourceId,
|
||||
siteResourceId: aiSessionLog.siteResourceId,
|
||||
userId: aiSessionLog.userId,
|
||||
requestedModel: aiSessionLog.requestedModel,
|
||||
isStream: aiSessionLog.isStream,
|
||||
requestBody: aiSessionLog.requestBody,
|
||||
responseBody: aiSessionLog.responseBody,
|
||||
normalizedRequest: aiSessionLog.normalizedRequest,
|
||||
normalizedResponse: aiSessionLog.normalizedResponse,
|
||||
truncated: aiSessionLog.truncated,
|
||||
statusCode: aiSessionLog.statusCode,
|
||||
createdAt: aiSessionLog.createdAt
|
||||
})
|
||||
.from(aiSessionLog)
|
||||
.where(getWhere(data))
|
||||
.orderBy(desc(aiSessionLog.createdAt));
|
||||
}
|
||||
|
||||
async function enrichWithDetails(
|
||||
logs: Awaited<ReturnType<typeof queryAiSession>>
|
||||
) {
|
||||
const providerIds = [...new Set(logs.map((log) => log.providerId))];
|
||||
|
||||
const resourceIds = logs
|
||||
.map((log) => log.resourceId)
|
||||
.filter((id): id is number => id !== null && id !== undefined);
|
||||
|
||||
const siteResourceIds = logs
|
||||
.filter((log) => log.resourceId == null && log.siteResourceId != null)
|
||||
.map((log) => log.siteResourceId)
|
||||
.filter((id): id is number => id !== null && id !== undefined);
|
||||
|
||||
const userIds = [
|
||||
...new Set(
|
||||
logs
|
||||
.map((log) => log.userId)
|
||||
.filter((id): id is string => id !== null && id !== undefined)
|
||||
)
|
||||
];
|
||||
|
||||
const providerMap = new Map<
|
||||
number,
|
||||
{ name: string | null; type: string | null }
|
||||
>();
|
||||
if (providerIds.length > 0) {
|
||||
const providerDetails = await primaryDb
|
||||
.select({
|
||||
providerId: aiProviders.providerId,
|
||||
name: aiProviders.name,
|
||||
type: aiProviders.type
|
||||
})
|
||||
.from(aiProviders)
|
||||
.where(inArray(aiProviders.providerId, providerIds));
|
||||
|
||||
for (const p of providerDetails) {
|
||||
providerMap.set(p.providerId, { name: p.name, type: p.type });
|
||||
}
|
||||
}
|
||||
|
||||
const resourceMap = new Map<
|
||||
number,
|
||||
{ name: string | null; niceId: string | null }
|
||||
>();
|
||||
if (resourceIds.length > 0) {
|
||||
const resourceDetails = await primaryDb
|
||||
.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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const userMap = new Map<string, string | null>();
|
||||
if (userIds.length > 0) {
|
||||
const userDetails = await db
|
||||
.select({ userId: users.userId, email: users.email })
|
||||
.from(users)
|
||||
.where(inArray(users.userId, userIds));
|
||||
|
||||
for (const u of userDetails) {
|
||||
userMap.set(u.userId, u.email);
|
||||
}
|
||||
}
|
||||
|
||||
return logs.map((log) => {
|
||||
const provider = providerMap.get(log.providerId);
|
||||
|
||||
let resourceId = log.resourceId;
|
||||
let resourceName: string | null = null;
|
||||
let resourceNiceId: string | null = null;
|
||||
let resourceType: "public" | "site" | null = null;
|
||||
if (log.resourceId != null) {
|
||||
const details = resourceMap.get(log.resourceId);
|
||||
resourceName = details?.name ?? null;
|
||||
resourceNiceId = details?.niceId ?? null;
|
||||
resourceType = "public";
|
||||
} else if (log.siteResourceId != null) {
|
||||
const details = siteResourceMap.get(log.siteResourceId);
|
||||
resourceId = log.siteResourceId;
|
||||
resourceName = details?.name ?? null;
|
||||
resourceNiceId = details?.niceId ?? null;
|
||||
resourceType = "site";
|
||||
}
|
||||
|
||||
return {
|
||||
...log,
|
||||
resourceId,
|
||||
resourceType,
|
||||
providerName: provider?.name ?? null,
|
||||
providerType: provider?.type ?? null,
|
||||
resourceName,
|
||||
resourceNiceId,
|
||||
userEmail: log.userId ? (userMap.get(log.userId) ?? null) : null
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function countAiSessionQuery(data: Q) {
|
||||
return logsDb
|
||||
.select({ count: count() })
|
||||
.from(aiSessionLog)
|
||||
.where(getWhere(data));
|
||||
}
|
||||
|
||||
registry.registerPath({
|
||||
method: "get",
|
||||
path: "/org/{orgId}/logs/ai",
|
||||
description: "Query the AI gateway session log for an organization",
|
||||
tags: [OpenAPITags.Logs],
|
||||
request: {
|
||||
query: queryAiSessionLogsQuery,
|
||||
params: queryAiSessionLogsParams
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
description: "Successful response",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: z.object({
|
||||
data: z.record(z.string(), z.any()).nullable(),
|
||||
success: z.boolean(),
|
||||
error: z.boolean(),
|
||||
message: z.string(),
|
||||
status: z.number()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function queryUniqueFilterAttributes(
|
||||
timeStart: number,
|
||||
timeEnd: number,
|
||||
orgId: string
|
||||
) {
|
||||
const baseConditions = and(
|
||||
gt(aiSessionLog.createdAt, timeStart),
|
||||
lt(aiSessionLog.createdAt, timeEnd),
|
||||
eq(aiSessionLog.orgId, orgId)
|
||||
);
|
||||
|
||||
const DISTINCT_LIMIT = 500;
|
||||
|
||||
const [
|
||||
uniqueProviders,
|
||||
uniqueUsers,
|
||||
uniqueResources,
|
||||
uniqueSiteResources
|
||||
] = await Promise.all([
|
||||
logsDb
|
||||
.selectDistinct({ id: aiSessionLog.providerId })
|
||||
.from(aiSessionLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
logsDb
|
||||
.selectDistinct({ userId: aiSessionLog.userId })
|
||||
.from(aiSessionLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
logsDb
|
||||
.selectDistinct({ id: aiSessionLog.resourceId })
|
||||
.from(aiSessionLog)
|
||||
.where(baseConditions)
|
||||
.limit(DISTINCT_LIMIT + 1),
|
||||
logsDb
|
||||
.selectDistinct({ id: aiSessionLog.siteResourceId })
|
||||
.from(aiSessionLog)
|
||||
.where(and(baseConditions, isNull(aiSessionLog.resourceId)))
|
||||
.limit(DISTINCT_LIMIT + 1)
|
||||
]);
|
||||
|
||||
const providerIds = uniqueProviders
|
||||
.map((row) => row.id)
|
||||
.filter((id): id is number => id !== null);
|
||||
|
||||
let providers: Array<{ id: number; name: string | null }> = [];
|
||||
if (providerIds.length > 0) {
|
||||
const providerDetails = await primaryDb
|
||||
.select({
|
||||
providerId: aiProviders.providerId,
|
||||
name: aiProviders.name
|
||||
})
|
||||
.from(aiProviders)
|
||||
.where(inArray(aiProviders.providerId, providerIds));
|
||||
|
||||
providers = providerDetails.map((p) => ({
|
||||
id: p.providerId,
|
||||
name: p.name
|
||||
}));
|
||||
}
|
||||
|
||||
const userIds = uniqueUsers
|
||||
.map((row) => row.userId)
|
||||
.filter((id): id is string => id !== null);
|
||||
|
||||
let userList: Array<{ id: string; email: string | null }> = [];
|
||||
if (userIds.length > 0) {
|
||||
const userDetails = await db
|
||||
.select({ userId: users.userId, email: users.email })
|
||||
.from(users)
|
||||
.where(inArray(users.userId, userIds));
|
||||
|
||||
userList = userDetails.map((u) => ({ id: u.userId, email: u.email }));
|
||||
}
|
||||
|
||||
const resourceIds = uniqueResources
|
||||
.map((row) => row.id)
|
||||
.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 }> = [];
|
||||
|
||||
if (resourceIds.length > 0) {
|
||||
const resourceDetails = await primaryDb
|
||||
.select({
|
||||
resourceId: resources.resourceId,
|
||||
name: resources.name
|
||||
})
|
||||
.from(resources)
|
||||
.where(inArray(resources.resourceId, resourceIds));
|
||||
|
||||
resourcesWithNames = [
|
||||
...resourcesWithNames,
|
||||
...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 {
|
||||
providers: sortNamedFilterOptions(providers),
|
||||
resources: sortNamedFilterOptions(resourcesWithNames),
|
||||
users: userList
|
||||
};
|
||||
}
|
||||
|
||||
export async function queryAiSessionLogs(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<any> {
|
||||
try {
|
||||
const parsedQuery = queryAiSessionLogsQuery.safeParse(req.query);
|
||||
if (!parsedQuery.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedQuery.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const parsedParams = queryAiSessionLogsParams.safeParse(req.params);
|
||||
if (!parsedParams.success) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
fromError(parsedParams.error)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const data = { ...parsedQuery.data, ...parsedParams.data };
|
||||
|
||||
const baseQuery = queryAiSession(data);
|
||||
|
||||
const logsRaw = await baseQuery.limit(data.limit).offset(data.offset);
|
||||
|
||||
const log = await enrichWithDetails(logsRaw);
|
||||
|
||||
const totalCountResult = await countAiSessionQuery(data);
|
||||
const totalCount = totalCountResult[0].count;
|
||||
|
||||
const filterAttributes = await queryUniqueFilterAttributes(
|
||||
data.timeStart,
|
||||
data.timeEnd,
|
||||
data.orgId
|
||||
);
|
||||
|
||||
return response<QueryAiSessionLogResponse>(res, {
|
||||
data: {
|
||||
log,
|
||||
pagination: {
|
||||
total: totalCount,
|
||||
limit: data.limit,
|
||||
offset: data.offset
|
||||
},
|
||||
filterAttributes
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message: "AI session logs retrieved successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return next(
|
||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -94,6 +94,53 @@ export type QueryAccessAuditLogResponse = {
|
||||
};
|
||||
};
|
||||
|
||||
export type QueryAiSessionLogResponse = {
|
||||
log: {
|
||||
id: number;
|
||||
sessionId: string;
|
||||
orgId: string | null;
|
||||
providerId: number;
|
||||
providerName: string | null;
|
||||
providerType: string | null;
|
||||
capability: string;
|
||||
resourceId: number | null;
|
||||
siteResourceId: number | null;
|
||||
resourceName: string | null;
|
||||
resourceNiceId: string | null;
|
||||
resourceType: "public" | "site" | null;
|
||||
userId: string | null;
|
||||
userEmail: string | null;
|
||||
requestedModel: string | null;
|
||||
isStream: boolean;
|
||||
requestBody: string | null;
|
||||
responseBody: string | null;
|
||||
normalizedRequest: string | null;
|
||||
normalizedResponse: string | null;
|
||||
truncated: boolean;
|
||||
statusCode: number | null;
|
||||
createdAt: number;
|
||||
}[];
|
||||
pagination: {
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
};
|
||||
filterAttributes: {
|
||||
providers: {
|
||||
id: number;
|
||||
name: string | null;
|
||||
}[];
|
||||
resources: {
|
||||
id: number;
|
||||
name: string | null;
|
||||
}[];
|
||||
users: {
|
||||
id: string;
|
||||
email: string | null;
|
||||
}[];
|
||||
};
|
||||
};
|
||||
|
||||
export type QueryConnectionAuditLogResponse = {
|
||||
log: {
|
||||
sessionId: string;
|
||||
|
||||
@@ -1487,6 +1487,21 @@ authenticated.get(
|
||||
logs.exportRequestAuditLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/logs/ai",
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.viewLogs),
|
||||
logs.queryAiSessionLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/logs/ai/export",
|
||||
verifyOrgAccess,
|
||||
verifyUserHasAction(ActionsEnum.exportLogs),
|
||||
logActionAudit(ActionsEnum.exportLogs),
|
||||
logs.exportAiSessionLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/blueprints",
|
||||
verifyOrgAccess,
|
||||
|
||||
@@ -1530,6 +1530,21 @@ authenticated.get(
|
||||
logs.exportRequestAuditLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/logs/ai",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.viewLogs),
|
||||
logs.queryAiSessionLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/logs/ai/export",
|
||||
verifyApiKeyOrgAccess,
|
||||
verifyApiKeyHasAction(ActionsEnum.exportLogs),
|
||||
logActionAudit(ActionsEnum.exportLogs),
|
||||
logs.exportAiSessionLogs
|
||||
);
|
||||
|
||||
authenticated.get(
|
||||
"/org/:orgId/logs/analytics",
|
||||
verifyApiKeyOrgAccess,
|
||||
|
||||
Reference in New Issue
Block a user