88 lines
3.2 KiB
TypeScript
88 lines
3.2 KiB
TypeScript
import { json } from "@remix-run/node";
|
|
import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
|
|
import { $replica } from "~/db.server";
|
|
import { findProjectBySlug } from "~/models/project.server";
|
|
import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server";
|
|
import {
|
|
LogsListOptionsSchema,
|
|
LogsListPresenter,
|
|
type LogLevel,
|
|
} from "~/presenters/v3/LogsListPresenter.server";
|
|
import { clickhouseFactory } from "~/services/clickhouse/clickhouseFactoryInstance.server";
|
|
import { getCurrentPlan } from "~/services/platform.v3.server";
|
|
import { requireUser } from "~/services/session.server";
|
|
import { EnvironmentParamSchema } from "~/utils/pathBuilder";
|
|
|
|
// Valid log levels for filtering
|
|
const validLevels: LogLevel[] = ["TRACE", "DEBUG", "INFO", "WARN", "ERROR"];
|
|
|
|
function parseLevelsFromUrl(url: URL): LogLevel[] | undefined {
|
|
const levelParams = url.searchParams.getAll("levels").filter((v) => v.length > 0);
|
|
if (levelParams.length === 0) return undefined;
|
|
return levelParams.filter((l): l is LogLevel => validLevels.includes(l as LogLevel));
|
|
}
|
|
|
|
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
|
const user = await requireUser(request);
|
|
const userId = user.id;
|
|
|
|
const { projectParam, organizationSlug, envParam } = EnvironmentParamSchema.parse(params);
|
|
|
|
const project = await findProjectBySlug(organizationSlug, projectParam, userId);
|
|
if (!project) {
|
|
throw new Response("Project not found", { status: 404 });
|
|
}
|
|
|
|
const environment = await findEnvironmentBySlug(project.id, envParam, userId);
|
|
if (!environment) {
|
|
throw new Response("Environment not found", { status: 404 });
|
|
}
|
|
|
|
// Get the user's plan to determine log retention limit
|
|
const plan = await getCurrentPlan(project.organizationId);
|
|
const retentionLimitDays = plan?.v3Subscription?.plan?.limits.logRetentionDays.number ?? 30;
|
|
|
|
// Get filters from query params
|
|
const url = new URL(request.url);
|
|
const tasks = url.searchParams.getAll("tasks").filter((t) => t.length > 0);
|
|
const runId = url.searchParams.get("runId") ?? undefined;
|
|
const search = url.searchParams.get("search") ?? undefined;
|
|
const cursor = url.searchParams.get("cursor") ?? undefined;
|
|
const levels = parseLevelsFromUrl(url);
|
|
const period = url.searchParams.get("period") ?? undefined;
|
|
const fromStr = url.searchParams.get("from");
|
|
const toStr = url.searchParams.get("to");
|
|
let from = fromStr ? parseInt(fromStr, 10) : undefined;
|
|
let to = toStr ? parseInt(toStr, 10) : undefined;
|
|
|
|
if (Number.isNaN(from)) from = undefined;
|
|
if (Number.isNaN(to)) to = undefined;
|
|
|
|
const options = LogsListOptionsSchema.parse({
|
|
userId,
|
|
projectId: project.id,
|
|
tasks: tasks.length > 0 ? tasks : undefined,
|
|
runId,
|
|
search,
|
|
cursor,
|
|
period,
|
|
from,
|
|
to,
|
|
levels,
|
|
defaultPeriod: "1h",
|
|
retentionLimitDays,
|
|
}) as any; // Validated by LogsListOptionsSchema at runtime
|
|
|
|
const logsClickhouse = await clickhouseFactory.getClickhouseForOrganization(
|
|
project.organizationId,
|
|
"logs"
|
|
);
|
|
const presenter = new LogsListPresenter($replica, logsClickhouse);
|
|
const result = await presenter.call(project.organizationId, environment.id, options);
|
|
|
|
return json({
|
|
logs: result.logs,
|
|
pagination: result.pagination,
|
|
});
|
|
};
|