2026-04-13 05:57:39 -06:00
|
|
|
|
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 ";
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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 };
|
2026-04-13 05:57:39 -06:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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 {
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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;
|
2026-04-13 05:57:39 -06:00
|
|
|
|
} 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[] {
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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 });
|
2026-04-13 05:57:39 -06:00
|
|
|
|
}
|
|
|
|
|
|
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 1–120 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 1–50" },
|
|
|
|
|
|
{ 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,
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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";
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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 });
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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;
|
2026-04-13 05:57:39 -06:00
|
|
|
|
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;
|
|
|
|
|
|
}
|