mc-dashboard/app/api/schedule/tasks/route.ts

273 lines
8 KiB
TypeScript
Raw Permalink Normal View History

import { NextRequest, NextResponse } from "next/server";
import { execSync } from "child_process";
import { writeFileSync, unlinkSync } from "fs";
import { randomBytes } from "crypto";
import { auth } from "@/lib/auth";
export const dynamic = "force-dynamic";
const TASK_MARKER = "# mc-task:";
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
const DISABLED_PREFIX = "#DISABLED ";
const RUN_SCRIPT = "/home/minecraft/dashboard/scripts/run-task.sh";
const TASK_TYPES = ["say", "backup", "snapshot-prune"] as const;
type TaskType = (typeof TASK_TYPES)[number];
type Task = {
id: string;
type: TaskType;
label: string;
cron: string; // "M H D M W"
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
enabled: boolean;
params: { message?: string; keep?: number };
};
const MSG_RE = /^[^\r\n$`\\'"]{1,120}$/;
function isValidCron(cron: string): boolean {
const parts = cron.trim().split(/\s+/);
if (parts.length !== 5) return false;
const field = /^(\*|\*\/\d+|\d+(?:[,/\-]\d+)*(?:,\d+(?:[,/\-]\d+)*)*)$/;
return parts.every((p) => field.test(p));
}
function escapeSingle(s: string): string {
return s.replace(/'/g, "'\\''");
}
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
export function buildCommand(task: Task): string {
const base = `bash ${RUN_SCRIPT}`;
if (task.type === "say") {
const msg = (task.params.message || "").trim();
return `${base} say '${escapeSingle(msg)}'`;
}
if (task.type === "snapshot-prune") {
const keep = Math.min(
Math.max(1, Math.floor(Number(task.params.keep) || 5)),
50
);
return `${base} snapshot-prune ${keep}`;
}
return `${base} backup`;
}
function encodeTask(task: Task): string {
return Buffer.from(JSON.stringify(task), "utf8").toString("base64");
}
function decodeTask(b64: string): Task | null {
try {
const obj = JSON.parse(Buffer.from(b64, "base64").toString("utf8"));
if (
!obj ||
typeof obj.id !== "string" ||
!TASK_TYPES.includes(obj.type) ||
typeof obj.cron !== "string" ||
typeof obj.label !== "string"
) {
return null;
}
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
return {
id: obj.id,
type: obj.type,
label: obj.label,
cron: obj.cron,
enabled: obj.enabled !== false,
params: obj.params || {},
} as Task;
} catch {
return null;
}
}
function readCrontab(): string {
try {
return execSync("crontab -l 2>/dev/null", { encoding: "utf8" });
} catch {
return "";
}
}
function writeCrontab(contents: string): void {
const tmp = `/tmp/crontab-${Date.now()}-${randomBytes(3).toString("hex")}.tmp`;
writeFileSync(tmp, contents.endsWith("\n") ? contents : contents + "\n", {
mode: 0o600,
});
try {
execSync(`crontab ${tmp}`, { encoding: "utf8" });
} finally {
try { unlinkSync(tmp); } catch {}
}
}
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
function renderLine(task: Task): string {
const cmd = buildCommand(task);
const body = `${task.cron} ${cmd} ${TASK_MARKER}${encodeTask(task)}`;
return task.enabled ? body : `${DISABLED_PREFIX}${body}`;
}
export function parseTasks(): Task[] {
const lines = readCrontab().split("\n");
const tasks: Task[] = [];
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
for (const rawLine of lines) {
let line = rawLine;
let enabled = true;
if (line.startsWith(DISABLED_PREFIX)) {
enabled = false;
line = line.slice(DISABLED_PREFIX.length);
}
const idx = line.indexOf(TASK_MARKER);
if (idx < 0) continue;
const b64 = line.slice(idx + TASK_MARKER.length).trim();
const task = decodeTask(b64);
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
if (task) tasks.push({ ...task, enabled });
}
return tasks;
}
export async function GET() {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
return NextResponse.json(parseTasks());
}
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
const body = await req.json();
const type = body.type as TaskType;
const label = typeof body.label === "string" ? body.label.trim().slice(0, 80) : "";
const cron = typeof body.cron === "string" ? body.cron.trim() : "";
const params = body.params || {};
if (!TASK_TYPES.includes(type)) {
return NextResponse.json({ error: "Invalid task type" }, { status: 400 });
}
if (!isValidCron(cron)) {
return NextResponse.json({ error: "Invalid cron expression" }, { status: 400 });
}
if (type === "say") {
if (typeof params.message !== "string" || !MSG_RE.test(params.message)) {
return NextResponse.json(
{ error: "Message must be 1120 chars, no quotes or newlines" },
{ status: 400 }
);
}
}
if (type === "snapshot-prune") {
const keep = Number(params.keep);
if (!Number.isInteger(keep) || keep < 1 || keep > 50) {
return NextResponse.json(
{ error: "keep must be an integer 150" },
{ status: 400 }
);
}
}
const task: Task = {
id: randomBytes(8).toString("hex"),
type,
label: label || defaultLabel(type, params),
cron,
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
enabled: true,
params: {
message: typeof params.message === "string" ? params.message : undefined,
keep: Number.isInteger(Number(params.keep)) ? Number(params.keep) : undefined,
},
};
try {
const crontab = readCrontab();
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
const updated =
(crontab ? crontab.replace(/\n+$/, "") + "\n" : "") +
renderLine(task) +
"\n";
writeCrontab(updated);
return NextResponse.json(task);
} catch (e) {
return NextResponse.json({ error: (e as Error).message }, { status: 500 });
}
}
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
export async function PATCH(req: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
const { id, enabled } = await req.json();
if (typeof id !== "string" || !/^[a-f0-9]{16}$/.test(id)) {
return NextResponse.json({ error: "Invalid id" }, { status: 400 });
}
if (typeof enabled !== "boolean") {
return NextResponse.json({ error: "enabled must be boolean" }, { status: 400 });
}
try {
const lines = readCrontab().split("\n");
let found: Task | null = null;
const updated = lines.map((rawLine) => {
let line = rawLine;
let wasDisabled = false;
if (line.startsWith(DISABLED_PREFIX)) {
wasDisabled = true;
line = line.slice(DISABLED_PREFIX.length);
}
const idx = line.indexOf(TASK_MARKER);
if (idx < 0) return rawLine;
const task = decodeTask(line.slice(idx + TASK_MARKER.length).trim());
if (!task || task.id !== id) return rawLine;
task.enabled = enabled;
found = task;
return renderLine(task);
void wasDisabled;
});
if (!found) {
return NextResponse.json({ error: "Task not found" }, { status: 404 });
}
writeCrontab(updated.filter(Boolean).join("\n"));
return NextResponse.json(found);
} catch (e) {
return NextResponse.json({ error: (e as Error).message }, { status: 500 });
}
}
export async function DELETE(req: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
const { id } = await req.json();
if (typeof id !== "string" || !/^[a-f0-9]{16}$/.test(id)) {
return NextResponse.json({ error: "Invalid id" }, { status: 400 });
}
try {
const lines = readCrontab().split("\n");
Bundle analyzer, task run-now/toggle/weekly, error reporter, OG image, a11y - Bundle analyzer: @next/bundle-analyzer wired through next.config.ts, new `bun run analyze` script (ANALYZE=true next build). - Scheduled tasks gain: - enabled flag round-tripped via a #DISABLED prefix on the crontab line (preserves the mc-task marker + payload for re-enable). - PATCH /api/schedule/tasks to toggle enabled. - POST /api/schedule/tasks/run to execute a task immediately via the same buildCommand used for the cron line (60s timeout, kills child on client abort). - Weekly preset in the UI (day-of-week selector), broader aria-labels on all form selects. Human-readable cron renders "Tue at 04:30" and next-run calc accounts for weekly. - Hover-reveal Run now / Enable / Disable / Remove actions; disabled tasks render at 60% opacity with a "disabled" badge and no next-run. - /api/errors: minimal append-only JSONL reporter (200ms throttle, UA and IP captured, fields length-bounded). Falls back to a stable path under /home/minecraft/logs/ and never fails the client on logging errors. - ErrorReporter client (production-only) listens to window.error and unhandledrejection, fingerprint-dedups via a bounded in-memory set, sends with keepalive so unloads still flush. - app/opengraph-image.tsx: 1200x630 dynamic PNG with live status dot (green/red), player count, address. 60s memo on the status probe. - A11y: aria-labels on log-line count select, scheduled-restart hour and minute selects, copy-server-address button (plus focus-visible ring). ModManager selected-mod pill upgraded to role=button with a real accessible name and a proper × glyph. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 06:08:48 -06:00
const kept = lines.filter((rawLine) => {
const line = rawLine.startsWith(DISABLED_PREFIX)
? rawLine.slice(DISABLED_PREFIX.length)
: rawLine;
const idx = line.indexOf(TASK_MARKER);
if (idx < 0) return true;
const task = decodeTask(line.slice(idx + TASK_MARKER.length).trim());
return !task || task.id !== id;
});
writeCrontab(kept.filter(Boolean).join("\n"));
return NextResponse.json({ ok: true });
} catch (e) {
return NextResponse.json({ error: (e as Error).message }, { status: 500 });
}
}
function defaultLabel(type: TaskType, params: { message?: string; keep?: number }): string {
if (type === "say") return `Announce: ${(params.message || "").slice(0, 40)}`;
if (type === "backup") return "World backup";
if (type === "snapshot-prune") return `Prune snapshots (keep ${params.keep || 5})`;
return type;
}