mirror of
https://github.com/fccview/cronmaster.git
synced 2025-12-26 23:48:15 -05:00
293 lines
7.6 KiB
TypeScript
293 lines
7.6 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { writeFile, readFile, unlink, mkdir } from "fs/promises";
|
|
import { join } from "path";
|
|
import { existsSync } from "fs";
|
|
import { exec } from "child_process";
|
|
import { promisify } from "util";
|
|
import { SCRIPTS_DIR } from "@/app/_utils/scripts";
|
|
import { loadAllScripts, type Script } from "@/app/_utils/scriptScanner";
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
export type { Script } from "@/app/_utils/scriptScanner";
|
|
|
|
function sanitizeScriptName(name: string): string {
|
|
return name
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9\s-]/g, "")
|
|
.replace(/\s+/g, "-")
|
|
.replace(/-+/g, "-")
|
|
.replace(/^-|-$/g, "")
|
|
.substring(0, 50);
|
|
}
|
|
|
|
async function generateUniqueFilename(baseName: string): Promise<string> {
|
|
const scripts = await loadAllScripts();
|
|
let filename = `${sanitizeScriptName(baseName)}.sh`;
|
|
let counter = 1;
|
|
|
|
while (scripts.some((script) => script.filename === filename)) {
|
|
filename = `${sanitizeScriptName(baseName)}-${counter}.sh`;
|
|
counter++;
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
async function ensureScriptsDirectory() {
|
|
if (!existsSync(SCRIPTS_DIR)) {
|
|
await mkdir(SCRIPTS_DIR, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function ensureHostScriptsDirectory() {
|
|
const isDocker = process.env.DOCKER === "true";
|
|
const hostScriptsDir = isDocker
|
|
? "/app/scripts"
|
|
: join(process.cwd(), "scripts");
|
|
if (!existsSync(hostScriptsDir)) {
|
|
await mkdir(hostScriptsDir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function saveScriptFile(filename: string, content: string) {
|
|
await ensureScriptsDirectory();
|
|
const scriptPath = join(SCRIPTS_DIR, filename);
|
|
await writeFile(scriptPath, content, "utf8");
|
|
}
|
|
|
|
async function deleteScriptFile(filename: string) {
|
|
const scriptPath = join(SCRIPTS_DIR, filename);
|
|
if (existsSync(scriptPath)) {
|
|
await unlink(scriptPath);
|
|
}
|
|
}
|
|
|
|
export async function fetchScripts(): Promise<Script[]> {
|
|
return await loadAllScripts();
|
|
}
|
|
|
|
export async function createScript(
|
|
formData: FormData
|
|
): Promise<{ success: boolean; message: string; script?: Script }> {
|
|
try {
|
|
const name = formData.get("name") as string;
|
|
const description = formData.get("description") as string;
|
|
const content = formData.get("content") as string;
|
|
|
|
if (!name || !content) {
|
|
return { success: false, message: "Name and content are required" };
|
|
}
|
|
|
|
const scriptId = `script_${Date.now()}_${Math.random()
|
|
.toString(36)
|
|
.substr(2, 9)}`;
|
|
const filename = await generateUniqueFilename(name);
|
|
|
|
const metadataHeader = `# @id: ${scriptId}
|
|
# @title: ${name}
|
|
# @description: ${description || ""}
|
|
|
|
`;
|
|
|
|
const fullContent = metadataHeader + content;
|
|
|
|
await saveScriptFile(filename, fullContent);
|
|
revalidatePath("/");
|
|
|
|
const newScript: Script = {
|
|
id: scriptId,
|
|
name,
|
|
description: description || "",
|
|
filename,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
message: "Script created successfully",
|
|
script: newScript,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error creating script:", error);
|
|
return { success: false, message: "Error creating script" };
|
|
}
|
|
}
|
|
|
|
export async function updateScript(
|
|
formData: FormData
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const id = formData.get("id") as string;
|
|
const name = formData.get("name") as string;
|
|
const description = formData.get("description") as string;
|
|
const content = formData.get("content") as string;
|
|
|
|
if (!id || !name || !content) {
|
|
return { success: false, message: "ID, name and content are required" };
|
|
}
|
|
|
|
const scripts = await loadAllScripts();
|
|
const existingScript = scripts.find((s) => s.id === id);
|
|
|
|
if (!existingScript) {
|
|
return { success: false, message: "Script not found" };
|
|
}
|
|
|
|
const metadataHeader = `# @id: ${id}
|
|
# @title: ${name}
|
|
# @description: ${description || ""}
|
|
|
|
`;
|
|
|
|
const fullContent = metadataHeader + content;
|
|
|
|
await saveScriptFile(existingScript.filename, fullContent);
|
|
revalidatePath("/");
|
|
|
|
return { success: true, message: "Script updated successfully" };
|
|
} catch (error) {
|
|
console.error("Error updating script:", error);
|
|
return { success: false, message: "Error updating script" };
|
|
}
|
|
}
|
|
|
|
export async function deleteScript(
|
|
id: string
|
|
): Promise<{ success: boolean; message: string }> {
|
|
try {
|
|
const scripts = await loadAllScripts();
|
|
const script = scripts.find((s) => s.id === id);
|
|
|
|
if (!script) {
|
|
return { success: false, message: "Script not found" };
|
|
}
|
|
|
|
await deleteScriptFile(script.filename);
|
|
revalidatePath("/");
|
|
|
|
return { success: true, message: "Script deleted successfully" };
|
|
} catch (error) {
|
|
console.error("Error deleting script:", error);
|
|
return { success: false, message: "Error deleting script" };
|
|
}
|
|
}
|
|
|
|
export async function cloneScript(
|
|
id: string,
|
|
newName: string
|
|
): Promise<{ success: boolean; message: string; script?: Script }> {
|
|
try {
|
|
const scripts = await loadAllScripts();
|
|
const originalScript = scripts.find((s) => s.id === id);
|
|
|
|
if (!originalScript) {
|
|
return { success: false, message: "Script not found" };
|
|
}
|
|
|
|
const scriptId = `script_${Date.now()}_${Math.random()
|
|
.toString(36)
|
|
.substr(2, 9)}`;
|
|
const filename = await generateUniqueFilename(newName);
|
|
|
|
const originalContent = await getScriptContent(originalScript.filename);
|
|
|
|
const metadataHeader = `# @id: ${scriptId}
|
|
# @title: ${newName}
|
|
# @description: ${originalScript.description}
|
|
|
|
`;
|
|
|
|
const fullContent = metadataHeader + originalContent;
|
|
|
|
await saveScriptFile(filename, fullContent);
|
|
revalidatePath("/");
|
|
|
|
const newScript: Script = {
|
|
id: scriptId,
|
|
name: newName,
|
|
description: originalScript.description,
|
|
filename,
|
|
createdAt: new Date().toISOString(),
|
|
};
|
|
|
|
return {
|
|
success: true,
|
|
message: "Script cloned successfully",
|
|
script: newScript,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error cloning script:", error);
|
|
return { success: false, message: "Error cloning script" };
|
|
}
|
|
}
|
|
|
|
export async function getScriptContent(filename: string): Promise<string> {
|
|
try {
|
|
const scriptPath = join(SCRIPTS_DIR, filename);
|
|
if (existsSync(scriptPath)) {
|
|
const content = await readFile(scriptPath, "utf8");
|
|
const lines = content.split("\n");
|
|
const contentLines: string[] = [];
|
|
|
|
let inMetadata = true;
|
|
for (const line of lines) {
|
|
if (line.trim().startsWith("# @")) {
|
|
continue;
|
|
}
|
|
if (line.trim() === "" && inMetadata) {
|
|
continue;
|
|
}
|
|
inMetadata = false;
|
|
contentLines.push(line);
|
|
}
|
|
|
|
return contentLines.join("\n").trim();
|
|
}
|
|
return "";
|
|
} catch (error) {
|
|
console.error("Error reading script content:", error);
|
|
return "";
|
|
}
|
|
}
|
|
|
|
export async function executeScript(filename: string): Promise<{
|
|
success: boolean;
|
|
output: string;
|
|
error: string;
|
|
}> {
|
|
try {
|
|
await ensureHostScriptsDirectory();
|
|
const isDocker = process.env.DOCKER === "true";
|
|
const hostScriptPath = isDocker
|
|
? join("/app/scripts", filename)
|
|
: join(process.cwd(), "scripts", filename);
|
|
|
|
if (!existsSync(hostScriptPath)) {
|
|
return {
|
|
success: false,
|
|
output: "",
|
|
error: "Script file not found",
|
|
};
|
|
}
|
|
|
|
const { stdout, stderr } = await execAsync(`bash "${hostScriptPath}"`, {
|
|
timeout: 30000,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
output: stdout,
|
|
error: stderr,
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
output: "",
|
|
error: error.message || "Unknown error",
|
|
};
|
|
}
|
|
}
|