mirror of
https://github.com/fccview/cronmaster.git
synced 2025-12-23 22:18:20 -05:00
Merge branch 'feature/1-5-0-improvements' into develop
This commit is contained in:
@@ -4,7 +4,7 @@ import { useState, useRef, useEffect, ReactNode } from "react";
|
||||
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
|
||||
import { MoreVertical } from "lucide-react";
|
||||
|
||||
const DROPDOWN_HEIGHT = 200; // Approximate max height of dropdown
|
||||
const DROPDOWN_HEIGHT = 200;
|
||||
|
||||
interface DropdownMenuItem {
|
||||
label: string;
|
||||
@@ -36,13 +36,11 @@ export const DropdownMenu = ({
|
||||
|
||||
const handleOpenChange = (open: boolean) => {
|
||||
if (open && triggerRef.current) {
|
||||
// Calculate if dropdown should be positioned above or below
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight;
|
||||
const spaceBelow = viewportHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
|
||||
// Position above if there's not enough space below
|
||||
setPositionAbove(spaceBelow < DROPDOWN_HEIGHT && spaceAbove > spaceBelow);
|
||||
}
|
||||
setIsOpen(open);
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
export const WRITE_CRONTAB = (content: string, user: string) => `echo '${content}' | crontab -u ${user} -`;
|
||||
export const WRITE_CRONTAB = (content: string, user: string) => {
|
||||
const escapedContent = content.replace(/'/g, "'\\''");
|
||||
return `crontab -u ${user} - << 'EOF'\n${escapedContent}\nEOF`;
|
||||
};
|
||||
|
||||
export const READ_CRONTAB = (user: string) => `crontab -l -u ${user} 2>/dev/null || echo ""`;
|
||||
export const READ_CRONTAB = (user: string) =>
|
||||
`crontab -l -u ${user} 2>/dev/null || echo ""`;
|
||||
|
||||
export const READ_CRON_FILE = () => 'crontab -l 2>/dev/null || echo ""'
|
||||
export const READ_CRON_FILE = () => 'crontab -l 2>/dev/null || echo ""';
|
||||
|
||||
export const WRITE_CRON_FILE = (content: string) => `echo "${content}" | crontab -`;
|
||||
export const WRITE_CRON_FILE = (content: string) => {
|
||||
const escapedContent = content.replace(/"/g, '\\"');
|
||||
return `echo "${escapedContent}" | crontab -`;
|
||||
};
|
||||
|
||||
export const WRITE_HOST_CRONTAB = (base64Content: string, user: string) => `echo '${base64Content}' | base64 -d | crontab -u ${user} -`;
|
||||
export const WRITE_HOST_CRONTAB = (base64Content: string, user: string) => {
|
||||
const escapedContent = base64Content.replace(/'/g, "'\\''");
|
||||
return `echo '${escapedContent}' | base64 -d | crontab -u ${user} -`;
|
||||
};
|
||||
|
||||
export const ID_U = (username: string) => `id -u ${username}`;
|
||||
|
||||
export const ID_G = (username: string) => `id -g ${username}`;
|
||||
|
||||
export const MAKE_SCRIPT_EXECUTABLE = (scriptPath: string) => `chmod +x "${scriptPath}"`;
|
||||
export const MAKE_SCRIPT_EXECUTABLE = (scriptPath: string) =>
|
||||
`chmod +x "${scriptPath}"`;
|
||||
|
||||
export const RUN_SCRIPT = (scriptPath: string) => `bash "${scriptPath}"`;
|
||||
|
||||
export const GET_TARGET_USER = `getent passwd | grep ":/home/" | head -1 | cut -d: -f1`
|
||||
export const GET_TARGET_USER = `getent passwd | grep ":/home/" | head -1 | cut -d: -f1`;
|
||||
|
||||
export const GET_DOCKER_SOCKET_OWNER = 'stat -c "%U" /var/run/docker.sock'
|
||||
export const GET_DOCKER_SOCKET_OWNER = 'stat -c "%U" /var/run/docker.sock';
|
||||
|
||||
export const READ_CRONTABS_DIRECTORY = `ls /var/spool/cron/crontabs/ 2>/dev/null || echo ''`;
|
||||
export const READ_CRONTABS_DIRECTORY = `ls /var/spool/cron/crontabs/ 2>/dev/null || echo ''`;
|
||||
|
||||
@@ -251,7 +251,13 @@ export const deleteCronJob = async (id: string): Promise<boolean> => {
|
||||
};
|
||||
|
||||
export const updateCronJob = async (
|
||||
jobData: { id: string; schedule: string; command: string; comment?: string; user: string },
|
||||
jobData: {
|
||||
id: string;
|
||||
schedule: string;
|
||||
command: string;
|
||||
comment?: string;
|
||||
user: string;
|
||||
},
|
||||
schedule: string,
|
||||
command: string,
|
||||
comment: string = "",
|
||||
@@ -275,7 +281,12 @@ export const updateCronJob = async (
|
||||
|
||||
if (logsEnabled && !isWrapped) {
|
||||
const docker = await isDocker();
|
||||
finalCommand = await wrapCommandWithLogger(jobData.id, command, docker, comment);
|
||||
finalCommand = await wrapCommandWithLogger(
|
||||
jobData.id,
|
||||
command,
|
||||
docker,
|
||||
comment
|
||||
);
|
||||
} else if (!logsEnabled && isWrapped) {
|
||||
finalCommand = unwrapCommand(command);
|
||||
} else if (logsEnabled && isWrapped) {
|
||||
@@ -390,7 +401,14 @@ export const cleanupCrontab = async (): Promise<boolean> => {
|
||||
};
|
||||
|
||||
export const findJobIndex = (
|
||||
jobData: { id: string; schedule: string; command: string; comment?: string; user: string; paused?: boolean },
|
||||
jobData: {
|
||||
id: string;
|
||||
schedule: string;
|
||||
command: string;
|
||||
comment?: string;
|
||||
user: string;
|
||||
paused?: boolean;
|
||||
},
|
||||
lines: string[],
|
||||
user: string
|
||||
): number => {
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
import { CronJob } from "@/app/_utils/cronjob-utils";
|
||||
import { generateShortUUID } from "@/app/_utils/uuid-utils";
|
||||
import { createHash } from "crypto";
|
||||
|
||||
const generateStableJobId = (
|
||||
schedule: string,
|
||||
command: string,
|
||||
user: string,
|
||||
comment?: string,
|
||||
lineIndex?: number
|
||||
): string => {
|
||||
const content = `${schedule}|${command}|${user}|${comment || ""}|${
|
||||
lineIndex || 0
|
||||
}`;
|
||||
const hash = createHash("md5").update(content).digest("hex");
|
||||
return hash.substring(0, 8);
|
||||
};
|
||||
|
||||
export const pauseJobInLines = (
|
||||
lines: string[],
|
||||
@@ -55,7 +70,11 @@ export const pauseJobInLines = (
|
||||
if (currentJobIndex === targetJobIndex) {
|
||||
const commentText = trimmedLine.substring(1).trim();
|
||||
const { comment, logsEnabled } = parseCommentMetadata(commentText);
|
||||
const formattedComment = formatCommentWithMetadata(comment, logsEnabled, uuid);
|
||||
const formattedComment = formatCommentWithMetadata(
|
||||
comment,
|
||||
logsEnabled,
|
||||
uuid
|
||||
);
|
||||
const nextLine = lines[i + 1].trim();
|
||||
const pausedEntry = `# PAUSED: ${formattedComment}\n# ${nextLine}`;
|
||||
newCronEntries.push(pausedEntry);
|
||||
@@ -128,8 +147,14 @@ export const resumeJobInLines = (
|
||||
const { comment, logsEnabled } = parseCommentMetadata(commentText);
|
||||
if (i + 1 < lines.length && lines[i + 1].trim().startsWith("# ")) {
|
||||
const cronLine = lines[i + 1].trim().substring(2);
|
||||
const formattedComment = formatCommentWithMetadata(comment, logsEnabled, uuid);
|
||||
const resumedEntry = formattedComment ? `# ${formattedComment}\n${cronLine}` : cronLine;
|
||||
const formattedComment = formatCommentWithMetadata(
|
||||
comment,
|
||||
logsEnabled,
|
||||
uuid
|
||||
);
|
||||
const resumedEntry = formattedComment
|
||||
? `# ${formattedComment}\n${cronLine}`
|
||||
: cronLine;
|
||||
newCronEntries.push(resumedEntry);
|
||||
i += 2;
|
||||
} else {
|
||||
@@ -175,7 +200,9 @@ export const parseCommentMetadata = (
|
||||
let uuid: string | undefined;
|
||||
|
||||
if (parts.length > 1) {
|
||||
const firstPartIsMetadata = parts[0].match(/logsEnabled:\s*(true|false)/i) || parts[0].match(/id:\s*([a-z0-9]{4}-[a-z0-9]{4})/i);
|
||||
const firstPartIsMetadata =
|
||||
parts[0].match(/logsEnabled:\s*(true|false)/i) ||
|
||||
parts[0].match(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/i);
|
||||
|
||||
if (firstPartIsMetadata) {
|
||||
comment = "";
|
||||
@@ -186,9 +213,11 @@ export const parseCommentMetadata = (
|
||||
logsEnabled = logsMatch[1].toLowerCase() === "true";
|
||||
}
|
||||
|
||||
const uuidMatch = metadata.match(/id:\s*([a-z0-9]{4}-[a-z0-9]{4})/i);
|
||||
if (uuidMatch) {
|
||||
uuid = uuidMatch[1].toLowerCase();
|
||||
const uuidMatches = Array.from(
|
||||
metadata.matchAll(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/gi)
|
||||
);
|
||||
if (uuidMatches.length > 0) {
|
||||
uuid = uuidMatches[uuidMatches.length - 1][1].toLowerCase();
|
||||
}
|
||||
} else {
|
||||
comment = parts[0] || "";
|
||||
@@ -199,14 +228,18 @@ export const parseCommentMetadata = (
|
||||
logsEnabled = logsMatch[1].toLowerCase() === "true";
|
||||
}
|
||||
|
||||
const uuidMatch = metadata.match(/id:\s*([a-z0-9]{4}-[a-z0-9]{4})/i);
|
||||
if (uuidMatch) {
|
||||
uuid = uuidMatch[1].toLowerCase();
|
||||
const uuidMatches = Array.from(
|
||||
metadata.matchAll(/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/gi)
|
||||
);
|
||||
if (uuidMatches.length > 0) {
|
||||
uuid = uuidMatches[uuidMatches.length - 1][1].toLowerCase();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const logsMatch = commentText.match(/logsEnabled:\s*(true|false)/i);
|
||||
const uuidMatch = commentText.match(/id:\s*([a-z0-9]{4}-[a-z0-9]{4})/i);
|
||||
const uuidMatch = commentText.match(
|
||||
/id:\s*([a-z0-9]{8}|[a-z0-9]{4}-[a-z0-9]{4})/i
|
||||
);
|
||||
|
||||
if (logsMatch || uuidMatch) {
|
||||
if (logsMatch) {
|
||||
@@ -288,7 +321,8 @@ export const parseJobsFromLines = (
|
||||
const schedule = parts.slice(0, 5).join(" ");
|
||||
const command = parts.slice(5).join(" ");
|
||||
|
||||
const jobId = uuid || generateShortUUID();
|
||||
const jobId =
|
||||
uuid || generateStableJobId(schedule, command, user, comment, i);
|
||||
|
||||
jobs.push({
|
||||
id: jobId,
|
||||
@@ -317,7 +351,8 @@ export const parseJobsFromLines = (
|
||||
lines[i + 1].trim()
|
||||
) {
|
||||
const commentText = trimmedLine.substring(1).trim();
|
||||
const { comment, logsEnabled, uuid } = parseCommentMetadata(commentText);
|
||||
const { comment, logsEnabled, uuid } =
|
||||
parseCommentMetadata(commentText);
|
||||
currentComment = comment;
|
||||
currentLogsEnabled = logsEnabled;
|
||||
currentUuid = uuid;
|
||||
@@ -343,7 +378,9 @@ export const parseJobsFromLines = (
|
||||
}
|
||||
|
||||
if (schedule && command) {
|
||||
const jobId = currentUuid || generateShortUUID();
|
||||
const jobId =
|
||||
currentUuid ||
|
||||
generateStableJobId(schedule, command, user, currentComment, i);
|
||||
|
||||
jobs.push({
|
||||
id: jobId,
|
||||
@@ -545,7 +582,11 @@ export const updateJobInLines = (
|
||||
}
|
||||
|
||||
if (currentJobIndex === targetJobIndex) {
|
||||
const formattedComment = formatCommentWithMetadata(comment, logsEnabled, uuid);
|
||||
const formattedComment = formatCommentWithMetadata(
|
||||
comment,
|
||||
logsEnabled,
|
||||
uuid
|
||||
);
|
||||
const newEntry = formattedComment
|
||||
? `# ${formattedComment}\n${schedule} ${command}`
|
||||
: `${schedule} ${command}`;
|
||||
|
||||
@@ -8,7 +8,6 @@ export default async function LoginPage() {
|
||||
const hasPassword = !!process.env.AUTH_PASSWORD;
|
||||
const hasOIDC = process.env.SSO_MODE === "oidc";
|
||||
|
||||
// Read package.json to get version
|
||||
const packageJsonPath = path.join(process.cwd(), "package.json");
|
||||
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
||||
const version = packageJson.version;
|
||||
|
||||
Reference in New Issue
Block a user