feat: MS23-P2-007 AuditLogDrawer + audit log endpoint
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { Type } from "class-transformer";
|
||||
import { IsInt, IsOptional, IsString, Max, Min } from "class-validator";
|
||||
|
||||
export class GetMissionControlAuditLogQueryDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sessionId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
page = 1;
|
||||
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(200)
|
||||
limit = 50;
|
||||
}
|
||||
@@ -18,9 +18,10 @@ import type { AgentMessage, AgentSession, InjectResult } from "@mosaic/shared";
|
||||
import { Observable } from "rxjs";
|
||||
import { AuthGuard } from "../../auth/guards/auth.guard";
|
||||
import { InjectAgentDto } from "../agents/dto/inject-agent.dto";
|
||||
import { GetMissionControlAuditLogQueryDto } from "./dto/get-mission-control-audit-log-query.dto";
|
||||
import { GetMissionControlMessagesQueryDto } from "./dto/get-mission-control-messages-query.dto";
|
||||
import { KillSessionDto } from "./dto/kill-session.dto";
|
||||
import { MissionControlService } from "./mission-control.service";
|
||||
import { MissionControlService, type MissionControlAuditLogPage } from "./mission-control.service";
|
||||
|
||||
const DEFAULT_OPERATOR_ID = "mission-control";
|
||||
|
||||
@@ -61,6 +62,14 @@ export class MissionControlController {
|
||||
return { messages };
|
||||
}
|
||||
|
||||
@Get("audit-log")
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
getAuditLog(
|
||||
@Query() query: GetMissionControlAuditLogQueryDto
|
||||
): Promise<MissionControlAuditLogPage> {
|
||||
return this.missionControlService.getAuditLog(query.sessionId, query.page, query.limit);
|
||||
}
|
||||
|
||||
@Post("sessions/:sessionId/inject")
|
||||
@HttpCode(200)
|
||||
@UsePipes(new ValidationPipe({ transform: true, whitelist: true }))
|
||||
|
||||
@@ -8,6 +8,24 @@ type MissionControlAction = "inject" | "pause" | "resume" | "kill";
|
||||
|
||||
const DEFAULT_OPERATOR_ID = "mission-control";
|
||||
|
||||
export interface AuditLogEntry {
|
||||
id: string;
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
provider: string;
|
||||
action: string;
|
||||
content: string | null;
|
||||
metadata: Prisma.JsonValue;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface MissionControlAuditLogPage {
|
||||
items: AuditLogEntry[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MissionControlService {
|
||||
constructor(
|
||||
@@ -33,6 +51,35 @@ export class MissionControlService {
|
||||
return provider.getMessages(sessionId, limit, before);
|
||||
}
|
||||
|
||||
async getAuditLog(
|
||||
sessionId: string | undefined,
|
||||
page: number,
|
||||
limit: number
|
||||
): Promise<MissionControlAuditLogPage> {
|
||||
const normalizedSessionId = sessionId?.trim();
|
||||
const where: Prisma.OperatorAuditLogWhereInput =
|
||||
normalizedSessionId && normalizedSessionId.length > 0
|
||||
? { sessionId: normalizedSessionId }
|
||||
: {};
|
||||
|
||||
const [total, items] = await this.prisma.$transaction([
|
||||
this.prisma.operatorAuditLog.count({ where }),
|
||||
this.prisma.operatorAuditLog.findMany({
|
||||
where,
|
||||
orderBy: { createdAt: "desc" },
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
total,
|
||||
page,
|
||||
pages: total === 0 ? 0 : Math.ceil(total / limit),
|
||||
};
|
||||
}
|
||||
|
||||
async injectMessage(
|
||||
sessionId: string,
|
||||
message: string,
|
||||
|
||||
Reference in New Issue
Block a user