From 2825243560abcebd1a3af831c590a45b7c745e13 Mon Sep 17 00:00:00 2001 From: fccview Date: Mon, 18 Aug 2025 13:15:13 +0100 Subject: [PATCH] implement snippets in a better, more clever way --- app/_components/BashSnippetHelper.tsx | 83 ++++- app/_components/modals/EditTaskModal.tsx | 8 - app/_server/actions/snippets/index.ts | 77 +++++ app/_utils/bashSnippets.ts | 357 --------------------- app/_utils/snippetScanner.ts | 162 ++++++++++ app/_utils/snippets/backup-rsync.sh | 18 ++ app/_utils/snippets/copy-files.sh | 23 ++ app/_utils/snippets/for-loop-files.sh | 19 ++ app/_utils/snippets/for-loop-range.sh | 18 ++ app/_utils/snippets/if-directory-exists.sh | 22 ++ app/_utils/snippets/if-else-basic.sh | 17 + app/_utils/snippets/if-file-exists.sh | 17 + app/_utils/snippets/log-rotation.sh | 28 ++ app/_utils/snippets/move-files.sh | 20 ++ app/_utils/snippets/mysql-backup.sh | 32 ++ app/_utils/snippets/postgres-backup.sh | 31 ++ app/_utils/snippets/process-monitor.sh | 26 ++ app/_utils/snippets/system-info.sh | 16 + app/_utils/snippets/while-loop-read.sh | 15 + docker-compose.yml | 2 + snippets/README.md | 66 ++++ snippets/example-user-snippet.sh | 16 + 22 files changed, 695 insertions(+), 378 deletions(-) create mode 100644 app/_server/actions/snippets/index.ts delete mode 100644 app/_utils/bashSnippets.ts create mode 100644 app/_utils/snippetScanner.ts create mode 100644 app/_utils/snippets/backup-rsync.sh create mode 100644 app/_utils/snippets/copy-files.sh create mode 100644 app/_utils/snippets/for-loop-files.sh create mode 100644 app/_utils/snippets/for-loop-range.sh create mode 100644 app/_utils/snippets/if-directory-exists.sh create mode 100644 app/_utils/snippets/if-else-basic.sh create mode 100644 app/_utils/snippets/if-file-exists.sh create mode 100644 app/_utils/snippets/log-rotation.sh create mode 100644 app/_utils/snippets/move-files.sh create mode 100644 app/_utils/snippets/mysql-backup.sh create mode 100644 app/_utils/snippets/postgres-backup.sh create mode 100644 app/_utils/snippets/process-monitor.sh create mode 100644 app/_utils/snippets/system-info.sh create mode 100644 app/_utils/snippets/while-loop-read.sh create mode 100644 snippets/README.md create mode 100644 snippets/example-user-snippet.sh diff --git a/app/_components/BashSnippetHelper.tsx b/app/_components/BashSnippetHelper.tsx index 635c71f..bcdb09a 100644 --- a/app/_components/BashSnippetHelper.tsx +++ b/app/_components/BashSnippetHelper.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { Button } from "./ui/Button"; import { Input } from "./ui/Input"; import { @@ -16,11 +16,11 @@ import { Check, } from "lucide-react"; import { - bashSnippets, - bashSnippetCategories, - searchBashSnippets, + fetchSnippets, + fetchSnippetCategories, + searchSnippets, type BashSnippet, -} from "../_utils/bashSnippets"; +} from "../_server/actions/snippets"; interface BashSnippetHelperProps { onInsertSnippet: (snippet: string) => void; @@ -32,18 +32,57 @@ const categoryIcons = { Conditionals: Code, "System Operations": Settings, "Database Operations": Database, + "User Examples": FolderOpen, + "Custom Scripts": Code, }; export function BashSnippetHelper({ onInsertSnippet }: BashSnippetHelperProps) { const [searchQuery, setSearchQuery] = useState(""); const [selectedCategory, setSelectedCategory] = useState(null); const [copiedId, setCopiedId] = useState(null); + const [snippets, setSnippets] = useState([]); + const [categories, setCategories] = useState([]); + const [filteredSnippets, setFilteredSnippets] = useState([]); + const [loading, setLoading] = useState(true); - const filteredSnippets = searchQuery - ? searchBashSnippets(searchQuery) - : selectedCategory - ? bashSnippets.filter((s) => s.category === selectedCategory) - : bashSnippets; + // Load snippets and categories on mount + useEffect(() => { + const loadData = async () => { + try { + const [snippetsData, categoriesData] = await Promise.all([ + fetchSnippets(), + fetchSnippetCategories(), + ]); + setSnippets(snippetsData); + setCategories(categoriesData); + } catch (error) { + console.error("Error loading snippets:", error); + } finally { + setLoading(false); + } + }; + + loadData(); + }, []); + + // Filter snippets based on search and category + useEffect(() => { + const filterSnippets = async () => { + if (searchQuery) { + const searchResults = await searchSnippets(searchQuery); + setFilteredSnippets(searchResults); + } else if (selectedCategory) { + const categoryResults = snippets.filter( + (s) => s.category === selectedCategory + ); + setFilteredSnippets(categoryResults); + } else { + setFilteredSnippets(snippets); + } + }; + + filterSnippets(); + }, [searchQuery, selectedCategory, snippets]); const handleCopy = async (snippet: BashSnippet) => { await navigator.clipboard.writeText(snippet.template); @@ -55,6 +94,17 @@ export function BashSnippetHelper({ onInsertSnippet }: BashSnippetHelperProps) { onInsertSnippet(snippet.template); }; + if (loading) { + return ( +
+
+ +

Loading snippets...

+
+
+ ); + } + return (
{/* Search */} @@ -80,8 +130,9 @@ export function BashSnippetHelper({ onInsertSnippet }: BashSnippetHelperProps) { > All - {bashSnippetCategories.map((category) => { - const Icon = categoryIcons[category as keyof typeof categoryIcons]; + {categories.map((category) => { + const Icon = + categoryIcons[category as keyof typeof categoryIcons] || Code; return (
- - {/* Bash Snippets Helper */} -
-

- 💡 Useful Bash Snippets -

- -
diff --git a/app/_server/actions/snippets/index.ts b/app/_server/actions/snippets/index.ts new file mode 100644 index 0000000..dd0054f --- /dev/null +++ b/app/_server/actions/snippets/index.ts @@ -0,0 +1,77 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { + loadAllSnippets, + searchBashSnippets, + getSnippetCategories, + getSnippetById, + type BashSnippet, +} from "@/app/_utils/snippetScanner"; + +export { type BashSnippet } from "@/app/_utils/snippetScanner"; + +export async function fetchSnippets(): Promise { + try { + return await loadAllSnippets(); + } catch (error) { + console.error("Error loading snippets:", error); + return []; + } +} + +export async function searchSnippets(query: string): Promise { + try { + const snippets = await loadAllSnippets(); + return searchBashSnippets(snippets, query); + } catch (error) { + console.error("Error searching snippets:", error); + return []; + } +} + +export async function fetchSnippetCategories(): Promise { + try { + const snippets = await loadAllSnippets(); + return getSnippetCategories(snippets); + } catch (error) { + console.error("Error loading snippet categories:", error); + return []; + } +} + +export async function fetchSnippetById( + id: string +): Promise { + try { + const snippets = await loadAllSnippets(); + return getSnippetById(snippets, id); + } catch (error) { + console.error("Error loading snippet by ID:", error); + return undefined; + } +} + +export async function fetchSnippetsByCategory( + category: string +): Promise { + try { + const snippets = await loadAllSnippets(); + return snippets.filter((snippet) => snippet.category === category); + } catch (error) { + console.error("Error loading snippets by category:", error); + return []; + } +} + +export async function fetchSnippetsBySource( + source: "builtin" | "user" +): Promise { + try { + const snippets = await loadAllSnippets(); + return snippets.filter((snippet) => snippet.source === source); + } catch (error) { + console.error("Error loading snippets by source:", error); + return []; + } +} diff --git a/app/_utils/bashSnippets.ts b/app/_utils/bashSnippets.ts deleted file mode 100644 index 6a24746..0000000 --- a/app/_utils/bashSnippets.ts +++ /dev/null @@ -1,357 +0,0 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -export interface BashSnippet { - id: string; - title: string; - description: string; - category: string; - template: string; - tags: string[]; -} - -export const bashSnippets: BashSnippet[] = [ - // File Operations - { - id: "backup-rsync", - title: "Backup with rsync", - description: - "Create a backup using rsync with progress and exclude options", - category: "File Operations", - template: `# Backup source directory to destination -# Change SOURCE_DIR and DEST_DIR to your paths -SOURCE_DIR="/path/to/source" -DEST_DIR="/path/to/backup" - -rsync -av --progress --delete \\ - --exclude='*.tmp' \\ - --exclude='*.log' \\ - --exclude='node_modules' \\ - "$SOURCE_DIR/" "$DEST_DIR/" - -echo "Backup completed at $(date)"`, - tags: ["backup", "rsync", "sync", "copy"], - }, - { - id: "copy-files", - title: "Copy files with confirmation", - description: "Copy files with interactive confirmation and error handling", - category: "File Operations", - template: `# Copy files with confirmation -# Change SOURCE and DEST to your paths -SOURCE="/path/to/source" -DEST="/path/to/destination" - -if [ -f "$SOURCE" ]; then - cp -i "$SOURCE" "$DEST" - if [ $? -eq 0 ]; then - echo "File copied successfully" - else - echo "Error copying file" - exit 1 - fi -else - echo "Source file does not exist" - exit 1 -fi`, - tags: ["copy", "cp", "file", "confirmation"], - }, - { - id: "move-files", - title: "Move files safely", - description: "Move files with backup and error handling", - category: "File Operations", - template: `# Move files with backup -# Change SOURCE and DEST to your paths -SOURCE="/path/to/source" -DEST="/path/to/destination" - -# Create backup before moving -if [ -f "$SOURCE" ]; then - cp "$SOURCE" "\${SOURCE}.backup.\$(date +%Y%m%d_%H%M%S)" - mv "$SOURCE" "$DEST" - echo "File moved successfully with backup created" -else - echo "Source file does not exist" - exit 1 -fi`, - tags: ["move", "mv", "backup", "safe"], - }, - - // Loops - { - id: "for-loop-files", - title: "For loop through files", - description: "Process multiple files in a directory", - category: "Loops", - template: `# Loop through files in a directory -# Change DIRECTORY to your target directory -DIRECTORY="/path/to/files" - -for file in "$DIRECTORY"/*; do - if [ -f "$file" ]; then - echo "Processing: $file" - # Add your processing logic here - # Example: process_file "$file" - fi -done - -echo "All files processed"`, - tags: ["loop", "files", "for", "process"], - }, - { - id: "while-loop-read", - title: "While loop reading input", - description: "Read input line by line and process it", - category: "Loops", - template: `# Read input line by line -# You can pipe input: echo "line1\\nline2" | ./script.sh -while IFS= read -r line; do - echo "Processing line: $line" - # Add your processing logic here - # Example: process_line "$line" -done - -echo "Finished processing all input"`, - tags: ["loop", "while", "read", "input"], - }, - { - id: "for-loop-range", - title: "For loop with range", - description: "Loop through a range of numbers", - category: "Loops", - template: `# Loop through a range of numbers -# Change START and END to your desired range -START=1 -END=10 - -for i in $(seq $START $END); do - echo "Processing item $i" - # Add your processing logic here - # Example: process_item $i -done - -echo "Finished processing range $START to $END"`, - tags: ["loop", "range", "numbers", "seq"], - }, - - // Conditionals - { - id: "if-else-basic", - title: "Basic if/else condition", - description: "Simple conditional logic with error handling", - category: "Conditionals", - template: `# Basic if/else condition -# Change CONDITION to your actual condition -CONDITION="test" - -if [ "$CONDITION" = "test" ]; then - echo "Condition is true" - # Add your logic here -else - echo "Condition is false" - # Add your alternative logic here -fi`, - tags: ["if", "else", "condition", "basic"], - }, - { - id: "if-file-exists", - title: "Check if file exists", - description: "Check file existence and handle accordingly", - category: "Conditionals", - template: `# Check if file exists -# Change FILE_PATH to your file path -FILE_PATH="/path/to/file" - -if [ -f "$FILE_PATH" ]; then - echo "File exists: $FILE_PATH" - # Add your logic for existing file -else - echo "File does not exist: $FILE_PATH" - # Add your logic for missing file -fi`, - tags: ["if", "file", "exists", "check"], - }, - { - id: "if-directory-exists", - title: "Check if directory exists", - description: "Check directory existence and create if needed", - category: "Conditionals", - template: `# Check if directory exists and create if needed -# Change DIR_PATH to your directory path -DIR_PATH="/path/to/directory" - -if [ -d "$DIR_PATH" ]; then - echo "Directory exists: $DIR_PATH" -else - echo "Creating directory: $DIR_PATH" - mkdir -p "$DIR_PATH" - if [ $? -eq 0 ]; then - echo "Directory created successfully" - else - echo "Failed to create directory" - exit 1 - fi -fi`, - tags: ["if", "directory", "mkdir", "create"], - }, - - // System Operations - { - id: "system-info", - title: "System information", - description: "Get basic system information", - category: "System Operations", - template: `# Get system information -echo "=== System Information ===" -echo "Hostname: $(hostname)" -echo "OS: $(uname -s)" -echo "Kernel: $(uname -r)" -echo "Architecture: $(uname -m)" -echo "Uptime: $(uptime)" -echo "Memory: $(free -h | grep Mem | awk '{print $2}')" -echo "Disk Usage: $(df -h / | tail -1 | awk '{print $5}')" -echo "========================"`, - tags: ["system", "info", "hostname", "uptime"], - }, - { - id: "log-rotation", - title: "Log rotation", - description: "Rotate log files with compression", - category: "System Operations", - template: `# Rotate log files -# Change LOG_FILE to your log file path -LOG_FILE="/var/log/your-app.log" -MAX_SIZE="100M" - -# Check if log file exists and is larger than max size -if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $(numfmt --from=iec $MAX_SIZE) ]; then - echo "Rotating log file: $LOG_FILE" - - # Create backup with timestamp - mv "$LOG_FILE" "\${LOG_FILE}.\$(date +%Y%m%d_%H%M%S)" - - # Compress old log - gzip "\${LOG_FILE}.\$(date +%Y%m%d_%H%M%S)" - - # Create new log file - touch "$LOG_FILE" - - echo "Log rotation completed" -else - echo "Log file does not need rotation" -fi`, - tags: ["log", "rotation", "compress", "gzip"], - }, - { - id: "process-monitor", - title: "Process monitoring", - description: "Monitor a process and restart if needed", - category: "System Operations", - template: `# Monitor and restart process if needed -# Change PROCESS_NAME to your process name -PROCESS_NAME="your-process" -RESTART_CMD="systemctl restart your-service" - -if pgrep -x "$PROCESS_NAME" > /dev/null; then - echo "$PROCESS_NAME is running" -else - echo "$PROCESS_NAME is not running, restarting..." - $RESTART_CMD - - # Wait a moment and check again - sleep 5 - if pgrep -x "$PROCESS_NAME" > /dev/null; then - echo "$PROCESS_NAME restarted successfully" - else - echo "Failed to restart $PROCESS_NAME" - exit 1 - fi -fi`, - tags: ["process", "monitor", "restart", "service"], - }, - - // Database Operations - { - id: "mysql-backup", - title: "MySQL database backup", - description: "Create a MySQL database backup with timestamp", - category: "Database Operations", - template: `# MySQL database backup -# Change these variables to your database details -DB_NAME="your_database" -DB_USER="your_username" -DB_PASS="your_password" -BACKUP_DIR="/path/to/backups" - -# Create backup directory if it doesn't exist -mkdir -p "$BACKUP_DIR" - -# Create backup filename with timestamp -BACKUP_FILE="$BACKUP_DIR/\${DB_NAME}_\$(date +%Y%m%d_%H%M%S).sql" - -# Create the backup -mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILE" - -if [ $? -eq 0 ]; then - echo "Database backup created: $BACKUP_FILE" - - # Compress the backup - gzip "$BACKUP_FILE" - echo "Backup compressed: \${BACKUP_FILE}.gz" -else - echo "Database backup failed" - exit 1 -fi`, - tags: ["mysql", "database", "backup", "mysqldump"], - }, - { - id: "postgres-backup", - title: "PostgreSQL database backup", - description: "Create a PostgreSQL database backup with timestamp", - category: "Database Operations", - template: `# PostgreSQL database backup -# Change these variables to your database details -DB_NAME="your_database" -DB_USER="your_username" -BACKUP_DIR="/path/to/backups" - -# Create backup directory if it doesn't exist -mkdir -p "$BACKUP_DIR" - -# Create backup filename with timestamp -BACKUP_FILE="$BACKUP_DIR/\${DB_NAME}_\$(date +%Y%m%d_%H%M%S).sql" - -# Create the backup -pg_dump -U "$DB_USER" "$DB_NAME" > "$BACKUP_FILE" - -if [ $? -eq 0 ]; then - echo "Database backup created: $BACKUP_FILE" - - # Compress the backup - gzip "$BACKUP_FILE" - echo "Backup compressed: \${BACKUP_FILE}.gz" -else - echo "Database backup failed" - exit 1 -fi`, - tags: ["postgres", "postgresql", "database", "backup", "pg_dump"], - }, -]; - -export const bashSnippetCategories = [ - "File Operations", - "Loops", - "Conditionals", - "System Operations", - "Database Operations", -]; - -export function searchBashSnippets(query: string): BashSnippet[] { - const lowercaseQuery = query.toLowerCase(); - return bashSnippets.filter( - (snippet) => - snippet.title.toLowerCase().includes(lowercaseQuery) || - snippet.description.toLowerCase().includes(lowercaseQuery) || - snippet.tags.some((tag) => tag.toLowerCase().includes(lowercaseQuery)) || - snippet.category.toLowerCase().includes(lowercaseQuery) - ); -} diff --git a/app/_utils/snippetScanner.ts b/app/_utils/snippetScanner.ts new file mode 100644 index 0000000..da7d991 --- /dev/null +++ b/app/_utils/snippetScanner.ts @@ -0,0 +1,162 @@ +import { promises as fs } from "fs"; +import path from "path"; + +export interface BashSnippet { + id: string; + title: string; + description: string; + category: string; + template: string; + tags: string[]; + source: "builtin" | "user"; + filePath: string; +} + +interface SnippetMetadata { + id?: string; + title?: string; + description?: string; + category?: string; + tags?: string[]; +} + +function parseMetadata(content: string): SnippetMetadata { + const metadata: SnippetMetadata = {}; + const lines = content.split("\n"); + + for (const line of lines) { + const trimmed = line.trim(); + + // Parse metadata comments like # @id: value + const match = trimmed.match(/^#\s*@(\w+):\s*(.+)$/); + if (match) { + const [, key, value] = match; + switch (key) { + case "id": + metadata.id = value.trim(); + break; + case "title": + metadata.title = value.trim(); + break; + case "description": + metadata.description = value.trim(); + break; + case "category": + metadata.category = value.trim(); + break; + case "tags": + metadata.tags = value.split(",").map((tag) => tag.trim()); + break; + } + } + } + + return metadata; +} + +function extractTemplate(content: string): string { + const lines = content.split("\n"); + const templateLines: string[] = []; + let inTemplate = false; + + for (const line of lines) { + // Skip metadata comments + if (line.trim().match(/^#\s*@\w+:/)) { + continue; + } + + // Start template after first non-metadata line + if (!inTemplate && line.trim() && !line.trim().startsWith("# @")) { + inTemplate = true; + } + + if (inTemplate) { + templateLines.push(line); + } + } + + return templateLines.join("\n").trim(); +} + +async function scanSnippetDirectory( + dirPath: string, + source: "builtin" | "user" +): Promise { + const snippets: BashSnippet[] = []; + + try { + const files = await fs.readdir(dirPath); + + for (const file of files) { + if (file.endsWith(".sh")) { + const filePath = path.join(dirPath, file); + const content = await fs.readFile(filePath, "utf-8"); + const metadata = parseMetadata(content); + const template = extractTemplate(content); + + // Only include snippets with valid metadata + if ( + metadata.id && + metadata.title && + metadata.description && + metadata.category + ) { + snippets.push({ + id: metadata.id, + title: metadata.title, + description: metadata.description, + category: metadata.category, + template, + tags: metadata.tags || [], + source, + filePath, + }); + } + } + } + } catch (error) { + console.warn(`Warning: Could not scan directory ${dirPath}:`, error); + } + + return snippets; +} + +export async function loadAllSnippets(): Promise { + const builtinSnippets = await scanSnippetDirectory( + path.join(process.cwd(), "app", "_utils", "snippets"), + "builtin" + ); + + const userSnippets = await scanSnippetDirectory( + path.join(process.cwd(), "snippets"), + "user" + ); + + return [...builtinSnippets, ...userSnippets]; +} + +export function searchBashSnippets( + snippets: BashSnippet[], + query: string +): BashSnippet[] { + const lowercaseQuery = query.toLowerCase(); + return snippets.filter( + (snippet) => + snippet.title.toLowerCase().includes(lowercaseQuery) || + snippet.description.toLowerCase().includes(lowercaseQuery) || + snippet.tags.some((tag) => tag.toLowerCase().includes(lowercaseQuery)) || + snippet.category.toLowerCase().includes(lowercaseQuery) + ); +} + +export function getSnippetCategories(snippets: BashSnippet[]): string[] { + const categories = new Set(snippets.map((snippet) => snippet.category)); + return Array.from(categories).sort(); +} + +export function getSnippetById( + snippets: BashSnippet[], + id: string +): BashSnippet | undefined { + return snippets.find((snippet) => snippet.id === id); +} diff --git a/app/_utils/snippets/backup-rsync.sh b/app/_utils/snippets/backup-rsync.sh new file mode 100644 index 0000000..8fa2caa --- /dev/null +++ b/app/_utils/snippets/backup-rsync.sh @@ -0,0 +1,18 @@ +# @id: backup-rsync +# @title: Backup with rsync +# @description: Create a backup using rsync with progress and exclude options +# @category: File Operations +# @tags: backup,rsync,sync,copy + +# Backup source directory to destination +# Change SOURCE_DIR and DEST_DIR to your paths +SOURCE_DIR="/path/to/source" +DEST_DIR="/path/to/backup" + +rsync -av --progress --delete \ + --exclude='*.tmp' \ + --exclude='*.log' \ + --exclude='node_modules' \ + "$SOURCE_DIR/" "$DEST_DIR/" + +echo "Backup completed at $(date)" \ No newline at end of file diff --git a/app/_utils/snippets/copy-files.sh b/app/_utils/snippets/copy-files.sh new file mode 100644 index 0000000..a98b258 --- /dev/null +++ b/app/_utils/snippets/copy-files.sh @@ -0,0 +1,23 @@ +# @id: copy-files +# @title: Copy files with confirmation +# @description: Copy files with interactive confirmation and error handling +# @category: File Operations +# @tags: copy,cp,file,confirmation + +# Copy files with confirmation +# Change SOURCE and DEST to your paths +SOURCE="/path/to/source" +DEST="/path/to/destination" + +if [ -f "$SOURCE" ]; then + cp -i "$SOURCE" "$DEST" + if [ $? -eq 0 ]; then + echo "File copied successfully" + else + echo "Error copying file" + exit 1 + fi +else + echo "Source file does not exist" + exit 1 +fi \ No newline at end of file diff --git a/app/_utils/snippets/for-loop-files.sh b/app/_utils/snippets/for-loop-files.sh new file mode 100644 index 0000000..1e44604 --- /dev/null +++ b/app/_utils/snippets/for-loop-files.sh @@ -0,0 +1,19 @@ +# @id: for-loop-files +# @title: For loop through files +# @description: Process multiple files in a directory +# @category: Loops +# @tags: loop,files,for,process + +# Loop through files in a directory +# Change DIRECTORY to your target directory +DIRECTORY="/path/to/files" + +for file in "$DIRECTORY"/*; do + if [ -f "$file" ]; then + echo "Processing: $file" + # Add your processing logic here + # Example: process_file "$file" + fi +done + +echo "All files processed" \ No newline at end of file diff --git a/app/_utils/snippets/for-loop-range.sh b/app/_utils/snippets/for-loop-range.sh new file mode 100644 index 0000000..16ec400 --- /dev/null +++ b/app/_utils/snippets/for-loop-range.sh @@ -0,0 +1,18 @@ +# @id: for-loop-range +# @title: For loop with range +# @description: Loop through a range of numbers +# @category: Loops +# @tags: loop,range,numbers,seq + +# Loop through a range of numbers +# Change START and END to your desired range +START=1 +END=10 + +for i in $(seq $START $END); do + echo "Processing item $i" + # Add your processing logic here + # Example: process_item $i +done + +echo "Finished processing range $START to $END" \ No newline at end of file diff --git a/app/_utils/snippets/if-directory-exists.sh b/app/_utils/snippets/if-directory-exists.sh new file mode 100644 index 0000000..59d62dc --- /dev/null +++ b/app/_utils/snippets/if-directory-exists.sh @@ -0,0 +1,22 @@ +# @id: if-directory-exists +# @title: Check if directory exists +# @description: Check directory existence and create if needed +# @category: Conditionals +# @tags: if,directory,mkdir,create + +# Check if directory exists and create if needed +# Change DIR_PATH to your directory path +DIR_PATH="/path/to/directory" + +if [ -d "$DIR_PATH" ]; then + echo "Directory exists: $DIR_PATH" +else + echo "Creating directory: $DIR_PATH" + mkdir -p "$DIR_PATH" + if [ $? -eq 0 ]; then + echo "Directory created successfully" + else + echo "Failed to create directory" + exit 1 + fi +fi \ No newline at end of file diff --git a/app/_utils/snippets/if-else-basic.sh b/app/_utils/snippets/if-else-basic.sh new file mode 100644 index 0000000..89acd39 --- /dev/null +++ b/app/_utils/snippets/if-else-basic.sh @@ -0,0 +1,17 @@ +# @id: if-else-basic +# @title: Basic if/else condition +# @description: Simple conditional logic with error handling +# @category: Conditionals +# @tags: if,else,condition,basic + +# Basic if/else condition +# Change CONDITION to your actual condition +CONDITION="test" + +if [ "$CONDITION" = "test" ]; then + echo "Condition is true" + # Add your logic here +else + echo "Condition is false" + # Add your alternative logic here +fi \ No newline at end of file diff --git a/app/_utils/snippets/if-file-exists.sh b/app/_utils/snippets/if-file-exists.sh new file mode 100644 index 0000000..c9f1bbe --- /dev/null +++ b/app/_utils/snippets/if-file-exists.sh @@ -0,0 +1,17 @@ +# @id: if-file-exists +# @title: Check if file exists +# @description: Check file existence and handle accordingly +# @category: Conditionals +# @tags: if,file,exists,check + +# Check if file exists +# Change FILE_PATH to your file path +FILE_PATH="/path/to/file" + +if [ -f "$FILE_PATH" ]; then + echo "File exists: $FILE_PATH" + # Add your logic for existing file +else + echo "File does not exist: $FILE_PATH" + # Add your logic for missing file +fi \ No newline at end of file diff --git a/app/_utils/snippets/log-rotation.sh b/app/_utils/snippets/log-rotation.sh new file mode 100644 index 0000000..d0303e5 --- /dev/null +++ b/app/_utils/snippets/log-rotation.sh @@ -0,0 +1,28 @@ +# @id: log-rotation +# @title: Log rotation +# @description: Rotate log files with compression +# @category: System Operations +# @tags: log,rotation,compress,gzip + +# Rotate log files +# Change LOG_FILE to your log file path +LOG_FILE="/var/log/your-app.log" +MAX_SIZE="100M" + +# Check if log file exists and is larger than max size +if [ -f "$LOG_FILE" ] && [ $(stat -c%s "$LOG_FILE") -gt $(numfmt --from=iec $MAX_SIZE) ]; then + echo "Rotating log file: $LOG_FILE" + + # Create backup with timestamp + mv "$LOG_FILE" "${LOG_FILE}.$(date +%Y%m%d_%H%M%S)" + + # Compress old log + gzip "${LOG_FILE}.$(date +%Y%m%d_%H%M%S)" + + # Create new log file + touch "$LOG_FILE" + + echo "Log rotation completed" +else + echo "Log file does not need rotation" +fi \ No newline at end of file diff --git a/app/_utils/snippets/move-files.sh b/app/_utils/snippets/move-files.sh new file mode 100644 index 0000000..7e657b6 --- /dev/null +++ b/app/_utils/snippets/move-files.sh @@ -0,0 +1,20 @@ +# @id: move-files +# @title: Move files safely +# @description: Move files with backup and error handling +# @category: File Operations +# @tags: move,mv,backup,safe + +# Move files with backup +# Change SOURCE and DEST to your paths +SOURCE="/path/to/source" +DEST="/path/to/destination" + +# Create backup before moving +if [ -f "$SOURCE" ]; then + cp "$SOURCE" "${SOURCE}.backup.$(date +%Y%m%d_%H%M%S)" + mv "$SOURCE" "$DEST" + echo "File moved successfully with backup created" +else + echo "Source file does not exist" + exit 1 +fi \ No newline at end of file diff --git a/app/_utils/snippets/mysql-backup.sh b/app/_utils/snippets/mysql-backup.sh new file mode 100644 index 0000000..50e0f44 --- /dev/null +++ b/app/_utils/snippets/mysql-backup.sh @@ -0,0 +1,32 @@ +# @id: mysql-backup +# @title: MySQL database backup +# @description: Create a MySQL database backup with timestamp +# @category: Database Operations +# @tags: mysql,database,backup,mysqldump + +# MySQL database backup +# Change these variables to your database details +DB_NAME="your_database" +DB_USER="your_username" +DB_PASS="your_password" +BACKUP_DIR="/path/to/backups" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +# Create backup filename with timestamp +BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql" + +# Create the backup +mysqldump -u "$DB_USER" -p"$DB_PASS" "$DB_NAME" > "$BACKUP_FILE" + +if [ $? -eq 0 ]; then + echo "Database backup created: $BACKUP_FILE" + + # Compress the backup + gzip "$BACKUP_FILE" + echo "Backup compressed: ${BACKUP_FILE}.gz" +else + echo "Database backup failed" + exit 1 +fi \ No newline at end of file diff --git a/app/_utils/snippets/postgres-backup.sh b/app/_utils/snippets/postgres-backup.sh new file mode 100644 index 0000000..92a0f88 --- /dev/null +++ b/app/_utils/snippets/postgres-backup.sh @@ -0,0 +1,31 @@ +# @id: postgres-backup +# @title: PostgreSQL database backup +# @description: Create a PostgreSQL database backup with timestamp +# @category: Database Operations +# @tags: postgres,postgresql,database,backup,pg_dump + +# PostgreSQL database backup +# Change these variables to your database details +DB_NAME="your_database" +DB_USER="your_username" +BACKUP_DIR="/path/to/backups" + +# Create backup directory if it doesn't exist +mkdir -p "$BACKUP_DIR" + +# Create backup filename with timestamp +BACKUP_FILE="$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql" + +# Create the backup +pg_dump -U "$DB_USER" "$DB_NAME" > "$BACKUP_FILE" + +if [ $? -eq 0 ]; then + echo "Database backup created: $BACKUP_FILE" + + # Compress the backup + gzip "$BACKUP_FILE" + echo "Backup compressed: ${BACKUP_FILE}.gz" +else + echo "Database backup failed" + exit 1 +fi \ No newline at end of file diff --git a/app/_utils/snippets/process-monitor.sh b/app/_utils/snippets/process-monitor.sh new file mode 100644 index 0000000..b0359a1 --- /dev/null +++ b/app/_utils/snippets/process-monitor.sh @@ -0,0 +1,26 @@ +# @id: process-monitor +# @title: Process monitoring +# @description: Monitor a process and restart if needed +# @category: System Operations +# @tags: process,monitor,restart,service + +# Monitor and restart process if needed +# Change PROCESS_NAME to your process name +PROCESS_NAME="your-process" +RESTART_CMD="systemctl restart your-service" + +if pgrep -x "$PROCESS_NAME" > /dev/null; then + echo "$PROCESS_NAME is running" +else + echo "$PROCESS_NAME is not running, restarting..." + $RESTART_CMD + + # Wait a moment and check again + sleep 5 + if pgrep -x "$PROCESS_NAME" > /dev/null; then + echo "$PROCESS_NAME restarted successfully" + else + echo "Failed to restart $PROCESS_NAME" + exit 1 + fi +fi \ No newline at end of file diff --git a/app/_utils/snippets/system-info.sh b/app/_utils/snippets/system-info.sh new file mode 100644 index 0000000..276eab6 --- /dev/null +++ b/app/_utils/snippets/system-info.sh @@ -0,0 +1,16 @@ +# @id: system-info +# @title: System information +# @description: Get basic system information +# @category: System Operations +# @tags: system,info,hostname,uptime + +# Get system information +echo "=== System Information ===" +echo "Hostname: $(hostname)" +echo "OS: $(uname -s)" +echo "Kernel: $(uname -r)" +echo "Architecture: $(uname -m)" +echo "Uptime: $(uptime)" +echo "Memory: $(free -h | grep Mem | awk '{print $2}')" +echo "Disk Usage: $(df -h / | tail -1 | awk '{print $5}')" +echo "========================" \ No newline at end of file diff --git a/app/_utils/snippets/while-loop-read.sh b/app/_utils/snippets/while-loop-read.sh new file mode 100644 index 0000000..33fd48f --- /dev/null +++ b/app/_utils/snippets/while-loop-read.sh @@ -0,0 +1,15 @@ +# @id: while-loop-read +# @title: While loop reading input +# @description: Read input line by line and process it +# @category: Loops +# @tags: loop,while,read,input + +# Read input line by line +# You can pipe input: echo "line1\nline2" | ./script.sh +while IFS= read -r line; do + echo "Processing line: $line" + # Add your processing logic here + # Example: process_line "$line" +done + +echo "Finished processing all input" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index d7035ce..e49678a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,8 @@ services: - ${NEXT_PUBLIC_HOST_PROJECT_DIR}/scripts:/app/scripts # Mount data directory for persistence - ${NEXT_PUBLIC_HOST_PROJECT_DIR}/data:/app/data + # Mount snippets directory for user-defined snippets + - ${NEXT_PUBLIC_HOST_PROJECT_DIR}/snippets:/app/snippets # Run with host network to access system information network_mode: host # Run as root to access system commands (needed for cron operations) diff --git a/snippets/README.md b/snippets/README.md new file mode 100644 index 0000000..2391b80 --- /dev/null +++ b/snippets/README.md @@ -0,0 +1,66 @@ +# User Snippets Directory + +This directory allows you to create your own bash script snippets that will automatically be recognized by the Cronjob Manager application. + +## How to Create a Snippet + +1. Create a new `.sh` file in this directory +2. Add metadata comments at the top of the file using the following format: + +```bash +# @id: your-snippet-id +# @title: Your Snippet Title +# @description: A brief description of what this snippet does +# @category: Your Category +# @tags: tag1,tag2,tag3 + +# Your bash script content goes here +echo "Hello World!" +``` + +## Metadata Fields + +- **@id**: A unique identifier for your snippet (use lowercase, hyphens for spaces) +- **@title**: A human-readable title for your snippet +- **@description**: A brief description of what the snippet does +- **@category**: The category this snippet belongs to (e.g., "File Operations", "System Operations", etc.) +- **@tags**: Comma-separated list of tags for searching + +## Example + +Here's an example snippet file: + +```bash +# @id: my-custom-backup +# @title: My Custom Backup Script +# @description: A custom backup script for my specific needs +# @category: File Operations +# @tags: backup,custom,personal + +# My custom backup logic +SOURCE_DIR="/home/user/documents" +BACKUP_DIR="/backup/documents" + +rsync -av "$SOURCE_DIR/" "$BACKUP_DIR/" +echo "Custom backup completed at $(date)" +``` + +## Notes + +- Only files with `.sh` extension will be recognized +- All metadata fields are required for the snippet to be loaded +- The script content should start after the metadata comments +- Your snippets will appear alongside the built-in snippets in the application +- You can organize snippets into subdirectories if needed + +## Categories + +You can use any category name you want, but here are some common ones: + +- File Operations +- System Operations +- Database Operations +- Loops +- Conditionals +- User Examples +- Custom Scripts diff --git a/snippets/example-user-snippet.sh b/snippets/example-user-snippet.sh new file mode 100644 index 0000000..b51738b --- /dev/null +++ b/snippets/example-user-snippet.sh @@ -0,0 +1,16 @@ +# @id: example-user-snippet +# @title: Example User Snippet +# @description: This is an example of how users can create their own snippets +# @category: User Snippets +# @tags: example,user,custom,demo + +# This is an example user-created snippet +# Users can add their own .sh files to the ./snippets directory +# and they will automatically be recognized by the system + +echo "Hello from user snippet!" +echo "Current time: $(date)" +echo "Working directory: $(pwd)" + +# Add your custom logic here +# This could be any bash script you want to use as a template \ No newline at end of file