mirror of
https://github.com/JetSprow/J-Board-Lite.git
synced 2026-05-01 09:14:11 +05:30
feat: add log cleanup controls
This commit is contained in:
111
src/actions/admin/logs.ts
Normal file
111
src/actions/admin/logs.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { requireAdmin } from "@/lib/require-auth";
|
||||
import { actorFromSession, recordAuditLog } from "@/services/audit";
|
||||
import {
|
||||
cleanupExpiredLogs,
|
||||
cutoffFromDays,
|
||||
deleteLogEntry,
|
||||
logCleanupTargetLabels,
|
||||
logCleanupTargets,
|
||||
summarizeLogCleanup,
|
||||
type LogCleanupSummary,
|
||||
} from "@/services/log-cleanup";
|
||||
import { getErrorMessage } from "@/lib/errors";
|
||||
|
||||
const deleteTargets = logCleanupTargets.filter((target) => target !== "ALL") as [
|
||||
Exclude<(typeof logCleanupTargets)[number], "ALL">,
|
||||
...Exclude<(typeof logCleanupTargets)[number], "ALL">[],
|
||||
];
|
||||
|
||||
const deleteLogSchema = z.object({
|
||||
target: z.enum(deleteTargets),
|
||||
id: z.string().trim().min(1),
|
||||
});
|
||||
|
||||
const cleanupExpiredLogsSchema = z.object({
|
||||
target: z.enum(logCleanupTargets),
|
||||
cutoffDays: z.coerce.number().int().min(1).max(3650),
|
||||
});
|
||||
|
||||
export type CleanupExpiredLogsResult =
|
||||
| { ok: true; summary: LogCleanupSummary; message: string }
|
||||
| { ok: false; error: string };
|
||||
|
||||
function revalidateLogViews() {
|
||||
revalidatePath("/admin/audit-logs");
|
||||
revalidatePath("/admin/tasks");
|
||||
revalidatePath("/admin/traffic");
|
||||
revalidatePath("/admin/subscription-risk");
|
||||
revalidatePath("/admin/settings");
|
||||
revalidatePath("/admin/subscriptions");
|
||||
}
|
||||
|
||||
export async function deleteAdminLogEntry(input: {
|
||||
target: Exclude<(typeof logCleanupTargets)[number], "ALL">;
|
||||
id: string;
|
||||
}) {
|
||||
const session = await requireAdmin();
|
||||
const actor = actorFromSession(session);
|
||||
const { target, id } = deleteLogSchema.parse(input);
|
||||
|
||||
await deleteLogEntry({ target, id });
|
||||
|
||||
await recordAuditLog({
|
||||
actor,
|
||||
action: "logs.delete",
|
||||
targetType: "LogEntry",
|
||||
targetId: id,
|
||||
targetLabel: logCleanupTargetLabels[target],
|
||||
message: `删除${logCleanupTargetLabels[target]}记录`,
|
||||
metadata: { target, deletedId: id },
|
||||
});
|
||||
|
||||
revalidateLogViews();
|
||||
}
|
||||
|
||||
export async function cleanupExpiredAdminLogs(input: {
|
||||
target: (typeof logCleanupTargets)[number];
|
||||
cutoffDays: number;
|
||||
}): Promise<CleanupExpiredLogsResult> {
|
||||
try {
|
||||
const session = await requireAdmin();
|
||||
const actor = actorFromSession(session);
|
||||
const parsed = cleanupExpiredLogsSchema.parse(input);
|
||||
const cutoff = cutoffFromDays(parsed.cutoffDays);
|
||||
const summary = await cleanupExpiredLogs({
|
||||
target: parsed.target,
|
||||
cutoff,
|
||||
keepActiveRiskRestrictions: true,
|
||||
});
|
||||
|
||||
const message = summarizeLogCleanup(summary);
|
||||
|
||||
await prisma.appConfig.update({
|
||||
where: { id: "default" },
|
||||
data: { logCleanupLastRunAt: new Date() },
|
||||
}).catch(() => null);
|
||||
|
||||
await recordAuditLog({
|
||||
actor,
|
||||
action: "logs.cleanup",
|
||||
targetType: "LogCleanup",
|
||||
targetLabel: logCleanupTargetLabels[parsed.target],
|
||||
message: `手动清理 ${parsed.cutoffDays} 天前的${logCleanupTargetLabels[parsed.target]}:${message}`,
|
||||
metadata: {
|
||||
target: parsed.target,
|
||||
cutoffDays: parsed.cutoffDays,
|
||||
cutoff: cutoff.toISOString(),
|
||||
summary,
|
||||
},
|
||||
});
|
||||
|
||||
revalidateLogViews();
|
||||
return { ok: true, summary, message };
|
||||
} catch (error) {
|
||||
return { ok: false, error: getErrorMessage(error, "清理日志失败") };
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,8 @@ const settingsSchema = z.object({
|
||||
reminderDispatchIntervalMinutes: z.coerce.number().int().positive().optional(),
|
||||
trafficSyncEnabled: z.string().optional(),
|
||||
trafficSyncIntervalSeconds: z.coerce.number().int().min(10).optional(),
|
||||
logCleanupEnabled: z.string().optional(),
|
||||
logRetentionDays: z.coerce.number().int().min(1).max(3650).optional(),
|
||||
networkRecommendationsEnabled: z.string().optional(),
|
||||
networkInsightsEnabled: z.string().optional(),
|
||||
subscriptionRiskEnabled: z.string().optional(),
|
||||
@@ -119,6 +121,7 @@ function booleanSettingData(field: BooleanSettingField, value: boolean) {
|
||||
requireInviteCode: { requireInviteCode: value },
|
||||
autoReminderDispatchEnabled: { autoReminderDispatchEnabled: value },
|
||||
trafficSyncEnabled: { trafficSyncEnabled: value },
|
||||
logCleanupEnabled: { logCleanupEnabled: value },
|
||||
networkRecommendationsEnabled: { networkRecommendationsEnabled: value },
|
||||
networkInsightsEnabled: { networkInsightsEnabled: value },
|
||||
subscriptionRiskEnabled: { subscriptionRiskEnabled: value },
|
||||
@@ -189,6 +192,8 @@ function buildSettingsUpdate(parsed: z.infer<typeof settingsSchema>, current: Aw
|
||||
trafficSyncEnabled: optionalBoolean(parsed.trafficSyncEnabled, current.trafficSyncEnabled),
|
||||
trafficSyncIntervalSeconds:
|
||||
parsed.trafficSyncIntervalSeconds ?? current.trafficSyncIntervalSeconds,
|
||||
logCleanupEnabled: optionalBoolean(parsed.logCleanupEnabled, current.logCleanupEnabled),
|
||||
logRetentionDays: parsed.logRetentionDays ?? current.logRetentionDays,
|
||||
networkRecommendationsEnabled: optionalBoolean(
|
||||
parsed.networkRecommendationsEnabled,
|
||||
current.networkRecommendationsEnabled,
|
||||
|
||||
Reference in New Issue
Block a user