Merge branch 'feature/1-5-0-improvements' into develop

This commit is contained in:
fccview
2025-11-19 16:47:54 +00:00
5 changed files with 98 additions and 31 deletions

View File

@@ -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);

View File

@@ -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 ''`;

View File

@@ -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 => {

View File

@@ -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}`;

View File

@@ -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;