diff --git a/app.py b/app.py index ed9cf27..7287874 100644 --- a/app.py +++ b/app.py @@ -6,11 +6,11 @@ import html import base64 import hashlib import secrets -import datetime import subprocess import platform -from datetime import UTC +from datetime import datetime, timedelta import sys +import psutil # ===== Third-Party Imports ===== from flask import ( @@ -138,7 +138,7 @@ def log_admin_event(message: str): try: key = load_admin_key() cipher = Fernet(key) - timestamp = datetime.datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S") + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") encrypted = cipher.encrypt(f"[{timestamp}] {message}".encode()) with open(ADMIN_LOG_FILE, 'ab') as f: f.write(encrypted + b"\n") @@ -149,12 +149,12 @@ def log_admin_event(message: str): def cleanup_expired_files(): """Remove files older than MAX_FILE_AGE_DAYS.""" try: - now = datetime.datetime.now(UTC) + now = datetime.now() for fname in os.listdir(UPLOAD_FOLDER): if fname.endswith(".enc") or fname.endswith(".json"): path = os.path.join(UPLOAD_FOLDER, fname) try: - file_time = datetime.datetime.fromtimestamp(os.path.getmtime(path), UTC) + file_time = datetime.datetime.fromtimestamp(os.path.getmtime(path), ) age = (now - file_time).days if age > MAX_FILE_AGE_DAYS: os.remove(path) @@ -208,7 +208,7 @@ def handle_file_upload(request): meta = { 'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(), 'original_name': filename, - 'timestamp': datetime.datetime.now(UTC).isoformat() + 'timestamp': datetime.datetime.now().isoformat() } with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f: json.dump(meta, f) @@ -408,48 +408,30 @@ def admin_page(): cleanup_expired_files() routes = [rule.rule for rule in app.url_map.iter_rules() if rule.endpoint != 'static'] - # Get uptime based on OS - if platform.system() == "Windows": - try: - # Windows uptime using PowerShell - ps_command = "(Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime" - uptime_output = subprocess.check_output(["powershell", "-Command", ps_command], shell=True).decode() - # Convert the PowerShell DateTime to Python datetime - boot_time = datetime.datetime.strptime(uptime_output.strip(), "%A, %B %d, %Y %I:%M:%S %p") - # Make boot_time timezone-aware (assuming local time) - boot_time = boot_time.replace(tzinfo=datetime.timezone.utc) - current_time = datetime.datetime.now(UTC) - uptime = current_time - boot_time - uptime_str = f"{uptime.days} days, {uptime.seconds // 3600} hours, {(uptime.seconds % 3600) // 60} minutes" - except Exception as e: - print(f"[ERROR] Failed to get Windows uptime: {str(e)}") - uptime_str = "Unavailable" - else: - try: - # Try reading from /proc/uptime first - with open('/proc/uptime', 'r') as f: - uptime_seconds = float(f.readline().split()[0]) - days = int(uptime_seconds // 86400) - hours = int((uptime_seconds % 86400) // 3600) - minutes = int((uptime_seconds % 3600) // 60) - uptime_str = f"{days} days, {hours} hours, {minutes} minutes" - except Exception: - try: - # Fallback to uptime command if /proc/uptime fails - uptime_str = subprocess.check_output("uptime -p", shell=True).decode().strip() - except Exception as e: - print(f"[ERROR] Failed to get Linux uptime: {str(e)}") - uptime_str = "Unavailable" + now = datetime.now() + try: + boot_time = datetime.fromtimestamp(psutil.boot_time()) + + uptime = now - boot_time + days = uptime.days + hours, remainder = divmod(uptime.seconds, 3600) + minutes = remainder // 60 + uptime_str = f"{days} days, {hours} hours, {minutes} minutes" + except Exception as e: + print(f"[ERROR] Uptime calculation failed: {e}") + uptime_str = "Unavailable" server_info = { "uptime": uptime_str, - "time": datetime.datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"), - "python": platform.python_version(), - "debug": app.debug + "server_time": now.strftime("%Y-%m-%d %H:%M:%S"), + "python_version": platform.python_version(), + "debug_mode": app.debug } return render_template("admin.html", routes=routes, server_info=server_info) + + @app.route("/restart-server", methods=["POST"]) def restart_server(): """Restart the server.""" @@ -458,9 +440,7 @@ def restart_server(): try: if platform.system() == "Windows": - # Get the current process ID current_pid = os.getpid() - # Create a batch file to restart the server restart_script = f""" @echo off timeout /t 2 /nobreak @@ -470,33 +450,30 @@ def restart_server(): """ with open("restart.bat", "w") as f: f.write(restart_script) - - # Start the restart script and exit subprocess.Popen(["restart.bat"], shell=True) return jsonify({"message": "Server restart initiated"}), 200 else: - # For Linux/Unix systems, use a Python-based restart - # Get the current Python interpreter and script path + current_pid = os.getpid() python_path = sys.executable script_path = os.path.abspath(__file__) - current_pid = os.getpid() - - # Create a shell script to restart the server - restart_script = f"""#!/bin/bash - sleep 2 - kill -9 {current_pid} - export PRODUCTION=true - {python_path} {script_path} - """ - - # Write and make the script executable + + # Create a safer and cleaner restart script + restart_script = """#!/bin/bash +sleep 2 +PID=$1 +kill "$PID" +while kill -0 "$PID" 2>/dev/null; do sleep 0.5; done +export PRODUCTION=true +exec "$2" "$3" +""" + with open("restart.sh", "w") as f: f.write(restart_script) os.chmod("restart.sh", 0o755) - - # Start the restart script and exit - subprocess.Popen(["./restart.sh"], shell=True) + + subprocess.Popen(["./restart.sh", str(current_pid), python_path, script_path]) return jsonify({"message": "Server restart initiated"}), 200 + except Exception as e: print(f"[ERROR] Failed to restart server: {str(e)}") return jsonify({"error": f"Failed to restart server: {str(e)}"}), 500 diff --git a/requirements.txt b/requirements.txt index b74fdf8..d213d4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ flask==3.0.3 cryptography==42.0.5 waitress==2.1.2 werkzeug==3.0.1 +psutil>=5.9.0,<6.0.0 # nginx - Only needed for Nginx integration, not installed via pip # Run pip install -r requirements.txt \ No newline at end of file diff --git a/restart.bat b/restart.bat new file mode 100644 index 0000000..d686c93 --- /dev/null +++ b/restart.bat @@ -0,0 +1,7 @@ + + @echo off + timeout /t 2 /nobreak + taskkill /F /PID 15428 + set PRODUCTION=true + start "" "python" "app.py" + \ No newline at end of file diff --git a/restart.sh b/restart.sh new file mode 100644 index 0000000..1f1de94 --- /dev/null +++ b/restart.sh @@ -0,0 +1,17 @@ +#!/bin/bash +sleep 2 + +# Save current process PID +PID=$1 + +# Gracefully stop the current server +kill "$PID" + +# Wait until it exits +while kill -0 "$PID" 2>/dev/null; do + sleep 0.5 +done + +# Restart with the same interpreter and script +export PRODUCTION=true +exec "$2" "$3" diff --git a/static/css/styles.css b/static/css/styles.css index eb9977a..42ed193 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -1,7 +1,7 @@ /* ===== Global Reset ===== */ * { box-sizing: border-box; - margin: 3px; + gap: 6px !important; padding: 0; } @@ -21,21 +21,42 @@ body { } @media (max-width: 600px) { + #sitemap-section, + #password-change-section, + #server-update-section, + #server-status-section, + #server-logs-section, + #system-settings-section { + padding: 20px; + margin-bottom: 20px; + } + + #sitemap-section li, + #server-status-section li { + font-size: 0.9em; + padding: 6px; + } + + #logContainer { + font-size: 0.9em; + padding: 10px; + } + body { font-size: 11px; padding: 10px; } - .button-group { + .button-group, + .admin-button-grid { flex-direction: column; - gap: 12px; - align-items: stretch; + align-items: center; } - .button-group button { - width: 100%; - min-width: unset; - max-width: 100%; + .button-group button, + .admin-button-grid button { + min-width: 75%; + max-width: 75%; } header { @@ -48,7 +69,6 @@ body { .logo-container { flex-direction: column; align-items: center; - gap: 12px; } .logo-container img { @@ -77,9 +97,16 @@ body { .admin-button-grid { grid-template-columns: 1fr; } + + .status-list { + width: 100%; + max-width: 400px; + padding-left: 0; + list-style: none; + word-wrap: break-word; + overflow-wrap: break-word; + } } - - /* ===== Header ===== */ header { @@ -99,7 +126,6 @@ header { .logo-container { display: flex; align-items: center; - gap: 10px; /* optional: controls spacing between logo and text */ } .logo-container img { @@ -135,7 +161,6 @@ main { width: 100%; max-width: 800px; padding: 0; - gap: 0; } /* ===== Card Styling ===== */ @@ -153,12 +178,22 @@ main { display: flex !important; flex-direction: column; align-items: center; - gap: 0px; max-width: 725px; width: 100%; } +.status-list { + width: 100%; + max-width: 400px; + padding-left: 0; + list-style: none; + word-wrap: break-word; + overflow-wrap: break-word; +} + + /* ===== Inputs, Textareas, Selects ===== */ + button, select, input, @@ -182,6 +217,7 @@ input[type="file"] { color: #28E060; text-align: left; transition: 0.3s; + min-height: 50px; } select { @@ -193,10 +229,6 @@ textarea { resize: none; } -input[type="password"] { - min-height: 50px; -} - /* ===== File Input Customization ===== */ input[type="file"] { border: 2px dashed #28E060; @@ -247,7 +279,6 @@ select:focus { display: flex; flex-wrap: nowrap; justify-content: center; - gap: 15px; width: 100%; } @@ -287,7 +318,6 @@ button { .admin-button-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: var(--spacer); /* e.g., 20px between rows and columns */ justify-items: center; width: 100%; max-width: 640px; @@ -308,7 +338,6 @@ button { display: flex; align-items: center; justify-content: center; - gap: 20px; } .toggle-label { @@ -376,7 +405,6 @@ button { display: flex; align-items: center; justify-content: center; - gap: 12px; width: 100%; } @@ -524,8 +552,8 @@ footer { select, #input-text, #output-text { - width: 100%; - max-width: 90%; + width: 100% !important; + max-width: 90% !important; } } @@ -556,7 +584,6 @@ footer { display: flex; flex-direction: column; align-items: center; - gap: 8px; margin-top: 12px; margin-bottom: 12px; } @@ -590,12 +617,6 @@ form { align-items: center; } -form input { - min-height: 50px; - width: 80%; - max-width: 500px; - text-align: left; -} /* ===== Section Card Styling ===== */ section.card { @@ -620,7 +641,6 @@ section.card { display: flex; flex-direction: column; align-items: center; - gap: 20px; margin-bottom: 25px; max-width: 725px; width: 100%; @@ -774,7 +794,7 @@ section.card { #sitemap-section li, #server-status-section li { - margin-bottom: 10px; + margin-bottom: 6px; padding: 8px; background-color: #2c2f33; border-radius: 6px; @@ -810,28 +830,4 @@ section.card { background-color: #1e1e1e; border-radius: 12px; box-shadow: 0 0 15px #28E060; -} - -/* ===== Mobile Responsive Adjustments ===== */ -@media (max-width: 768px) { - #sitemap-section, - #password-change-section, - #server-update-section, - #server-status-section, - #server-logs-section, - #system-settings-section { - padding: 20px; - margin-bottom: 20px; - } - - #sitemap-section li, - #server-status-section li { - font-size: 0.9em; - padding: 6px; - } - - #logContainer { - font-size: 0.9em; - padding: 10px; - } -} +} \ No newline at end of file diff --git a/static/img/PacCrypt.png b/static/img/PacCrypt.png index 7b143f4..e0318ee 100644 Binary files a/static/img/PacCrypt.png and b/static/img/PacCrypt.png differ diff --git a/static/img/PacCrypt_W-Background.png b/static/img/PacCrypt_W-Background.png new file mode 100644 index 0000000..081558b Binary files /dev/null and b/static/img/PacCrypt_W-Background.png differ diff --git a/static/img/PacCrypt_W-Background_Name.png b/static/img/PacCrypt_W-Background_Name.png new file mode 100644 index 0000000..898563e Binary files /dev/null and b/static/img/PacCrypt_W-Background_Name.png differ diff --git a/static/img/PacCrypt_W-Name.png b/static/img/PacCrypt_W-Name.png new file mode 100644 index 0000000..fda9b45 Binary files /dev/null and b/static/img/PacCrypt_W-Name.png differ diff --git a/static/js/ui.js b/static/js/ui.js index 1778ede..b1be2c6 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -1,305 +1,353 @@ -/** - * UI management module. - * Handles user interface interactions and form handling. - */ - -import { encryptFile, decryptFile } from './fileops.js'; - -// ===== UI Initialization ===== -export function setupUI() { - // Set initial state of remove button to hidden - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) { - removeBtn.style.display = "none"; - } - - initializeEventListeners(); -} - -// ===== Event Listeners ===== -function initializeEventListeners() { - const elements = { - encryptionType: document.getElementById("encryption-type"), - inputText: document.getElementById("input-text"), - form: document.getElementById("crypto-form"), - removeFileBtn: document.getElementById("remove-file-btn"), - clearAllBtn: document.getElementById("clear-all-btn"), - generateBtn: document.getElementById("generate-btn"), - copyPasswordBtn: document.getElementById("copy-btn"), - copyOutputBtn: document.getElementById("copy-output-btn"), - toggleSwitch: document.getElementById("operation-toggle"), - copyShareBtn: document.getElementById("copy-share-btn"), - shareLink: document.getElementById("share-link") - }; - - if (validateElements(elements)) { - setupElementListeners(elements); - } -} - -function validateElements(elements) { - return elements.encryptionType && elements.inputText && elements.form && - elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn && - elements.copyPasswordBtn && elements.toggleSwitch; -} - -function setupElementListeners(elements) { - elements.encryptionType.addEventListener("change", toggleEncryptionOptions); - elements.inputText.addEventListener("input", handleInputChange); - elements.form.addEventListener("submit", handleSubmit); - elements.removeFileBtn.addEventListener("click", removeFile); - elements.clearAllBtn.addEventListener("click", clearAll); - elements.generateBtn.addEventListener("click", generateRandomPassword); - elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback")); - elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback")); - elements.toggleSwitch.addEventListener("change", () => { - console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt"); - }); - - - - // Add file input change listener - const fileInput = document.getElementById("file-input"); - if (fileInput) { - fileInput.addEventListener("change", () => { - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) { - removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none"; - } - }); - } - - setupShareLinkListeners(elements); -} - -function setupShareLinkListeners(elements) { - if (elements.copyShareBtn && elements.shareLink) { - elements.copyShareBtn.addEventListener("click", () => { - const linkText = elements.shareLink.textContent.trim(); - navigator.clipboard.writeText(linkText).then(() => { - const feedback = document.getElementById("shared-link-feedback"); - if (feedback) { - feedback.style.display = "block"; - feedback.classList.add("show"); - setTimeout(() => { - feedback.classList.remove("show"); - setTimeout(() => { - feedback.style.display = "none"; - }, 300); - }, 3000); - } - }); - }); - } -} - -// ===== UI State Management ===== -function toggleEncryptionOptions() { - const type = document.getElementById("encryption-type").value.trim().toLowerCase(); - const passwordInputWrapper = document.getElementById("password-input"); - const fileSection = document.querySelector("#encoding-section #file-section"); - const isAdvanced = type.includes("advanced"); - - if (passwordInputWrapper) { - if (isAdvanced) { - passwordInputWrapper.classList.remove("hidden"); - } else { - passwordInputWrapper.classList.add("hidden"); - } - } - - if (fileSection) { - if (isAdvanced) { - fileSection.classList.remove("hidden"); - } else { - fileSection.classList.add("hidden"); - } - } - - updateToggleLabels(); - toggleInputMode(); -} - -function updateToggleLabels() { - const type = document.getElementById("encryption-type")?.value; - const leftLabel = document.getElementById("toggle-left-label"); - const rightLabel = document.getElementById("toggle-right-label"); - - if (!type || !leftLabel || !rightLabel) return; - - const isAdvanced = type.toLowerCase().includes("advanced"); - leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode"; - rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode"; -} - -function toggleInputMode() { - const fileInput = document.getElementById("file-input"); - const textValue = document.getElementById("input-text")?.value.trim(); - const isAdvanced = document.getElementById("encryption-type")?.value === "advanced"; - - const textSection = document.getElementById("text-section"); - const fileSection = document.getElementById("file-section"); - const removeBtn = document.getElementById("remove-file-btn"); - - if (!fileInput || !textSection || !fileSection || !removeBtn) return; - - const fileSelected = fileInput.files.length > 0; - - textSection.style.display = fileSelected ? "none" : "flex"; - fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none"; - removeBtn.style.display = fileSelected ? "inline-block" : "none"; -} - -// ===== Form Handling ===== -async function handleSubmit(event) { - event.preventDefault(); - - const encryptionType = document.getElementById("encryption-type")?.value; - const password = document.getElementById("password")?.value; - const fileInput = document.getElementById("file-input"); - const isDecrypt = document.getElementById("operation-toggle").checked; - const operation = isDecrypt ? "decrypt" : "encrypt"; - - if (!encryptionType || !fileInput) return; - - if (encryptionType === "advanced" && !password) { - return alert("Password is required for advanced encryption."); - } - - if (fileInput.files.length > 0) { - return (operation === "encrypt") - ? encryptFile(fileInput, password) - : decryptFile(fileInput, password); - } - - await handleTextOperation(encryptionType, operation, password); -} - -async function handleTextOperation(encryptionType, operation, password) { - const payload = { - "encryption-type": encryptionType, - operation: operation, - message: document.getElementById("input-text")?.value, - password: password - }; - - try { - const response = await fetch("/", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(payload) - }); - const data = await response.json(); - document.getElementById("output-text").value = data.result; - } catch (err) { - alert("Error processing request: " + err.message); - } -} - -// ===== Utility Functions ===== -function removeFile() { - const fileInput = document.getElementById("file-input"); - if (fileInput) fileInput.value = ""; - const removeBtn = document.getElementById("remove-file-btn"); - if (removeBtn) removeBtn.style.display = 'none'; - toggleInputMode(); -} - -function generateRandomPassword() { - const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~"; - const length = 30; - const password = Array.from({ length }, () => - charset.charAt(Math.floor(Math.random() * charset.length)) - ).join(""); - const passwordField = document.getElementById("generated-password"); - if (passwordField) { - passwordField.value = password; - // Check if we should start Pacman - checkForPacman(); - } -} - -function copyToClipboard(elementId, feedbackId) { - const el = document.getElementById(elementId); - const feedback = document.getElementById(feedbackId); - - if (!el || !el.value) return; - - // Create a temporary textarea element - const textarea = document.createElement('textarea'); - textarea.value = el.value; - textarea.style.position = 'fixed'; - textarea.style.opacity = '0'; - document.body.appendChild(textarea); - - // Select and copy the text - textarea.select(); - textarea.setSelectionRange(0, 99999); // For mobile devices - - try { - // Try using the modern clipboard API first - navigator.clipboard.writeText(el.value).then(() => { - showFeedback(feedback); - }).catch(() => { - // Fallback to execCommand for older browsers - document.execCommand('copy'); - showFeedback(feedback); - }); - } catch (err) { - // Final fallback - document.execCommand('copy'); - showFeedback(feedback); - } - - // Clean up - document.body.removeChild(textarea); -} - -function showFeedback(feedback) { - if (feedback) { - feedback.style.display = "block"; - feedback.classList.add("show"); - setTimeout(() => { - feedback.classList.remove("show"); - setTimeout(() => { - feedback.style.display = "none"; - }, 300); - }, 3000); - } -} - -function clearAll() { - const fields = ["input-text", "output-text", "file-input", "password"]; - fields.forEach(id => { - const el = document.getElementById(id); - if (el) el.value = ""; - }); - removeFile(); - toggleInputMode(); - document.getElementById("pacman-section")?.style.setProperty("display", "none"); - document.getElementById("encoding-section")?.style.setProperty("display", "block"); -} - -function handleInputChange() { - toggleInputMode(); - checkForPacman(); -} - -function checkForPacman() { - const val = document.getElementById("input-text").value.trim().toLowerCase(); - const pacSection = document.getElementById("pacman-section"); - const encSection = document.getElementById("encoding-section"); - - if (val.includes("pacman") && pacSection.style.display !== "block") { - pacSection.style.display = "block"; - encSection.style.display = "none"; - window.startPacman(); - } else if (pacSection.style.display === "block" && !val.includes("pacman")) { - window.exitGame(); - } -} - -function startPacman() { } -function exitGame() { } - - +/** + * UI management module. + * Handles user interface interactions and form handling. + */ + +import { encryptFile, decryptFile } from './fileops.js'; + +// ===== UI Initialization ===== +export function setupUI() { + // Set initial state of remove button to hidden + const removeBtn = document.getElementById("remove-file-btn"); + if (removeBtn) { + removeBtn.style.display = "none"; + } + + initializeEventListeners(); +} + +// ===== Event Listeners ===== +function initializeEventListeners() { + const elements = { + encryptionType: document.getElementById("encryption-type"), + inputText: document.getElementById("input-text"), + form: document.getElementById("crypto-form"), + removeFileBtn: document.getElementById("remove-file-btn"), + clearAllBtn: document.getElementById("clear-all-btn"), + generateBtn: document.getElementById("generate-btn"), + copyPasswordBtn: document.getElementById("copy-btn"), + copyOutputBtn: document.getElementById("copy-output-btn"), + toggleSwitch: document.getElementById("operation-toggle"), + copyShareBtn: document.getElementById("copy-share-btn"), + shareLink: document.getElementById("share-link") + }; + + if (validateElements(elements)) { + setupElementListeners(elements); + } +} + +function validateElements(elements) { + return elements.encryptionType && elements.inputText && elements.form && + elements.removeFileBtn && elements.clearAllBtn && elements.generateBtn && + elements.copyPasswordBtn && elements.toggleSwitch; +} + +function setupElementListeners(elements) { + elements.encryptionType.addEventListener("change", toggleEncryptionOptions); + elements.inputText.addEventListener("input", handleInputChange); + elements.form.addEventListener("submit", handleSubmit); + elements.removeFileBtn.addEventListener("click", removeFile); + elements.clearAllBtn.addEventListener("click", clearAll); + elements.generateBtn.addEventListener("click", generateRandomPassword); + elements.copyPasswordBtn.addEventListener("click", () => copyToClipboard("generated-password", "password-copy-feedback")); + elements.copyOutputBtn?.addEventListener("click", () => copyToClipboard("output-text", "output-copy-feedback")); + elements.toggleSwitch.addEventListener("change", () => { + console.log("Mode:", elements.toggleSwitch.checked ? "Decrypt" : "Encrypt"); + }); + + + + // Add file input change listener + const fileInput = document.getElementById("file-input"); + if (fileInput) { + fileInput.addEventListener("change", () => { + const removeBtn = document.getElementById("remove-file-btn"); + if (removeBtn) { + removeBtn.style.display = fileInput.files.length > 0 ? "inline-block" : "none"; + } + }); + } + + setupShareLinkListeners(elements); +} + +function setupShareLinkListeners(elements) { + if (elements.copyShareBtn && elements.shareLink) { + elements.copyShareBtn.addEventListener("click", () => { + const linkText = elements.shareLink.textContent.trim(); + navigator.clipboard.writeText(linkText).then(() => { + const feedback = document.getElementById("shared-link-feedback"); + if (feedback) { + feedback.style.display = "block"; + feedback.classList.add("show"); + setTimeout(() => { + feedback.classList.remove("show"); + setTimeout(() => { + feedback.style.display = "none"; + }, 300); + }, 3000); + } + }); + }); + } +} + +// ===== UI State Management ===== +function toggleEncryptionOptions() { + const type = document.getElementById("encryption-type").value.trim().toLowerCase(); + const passwordInputWrapper = document.getElementById("password-input"); + const fileSection = document.querySelector("#encoding-section #file-section"); + const isAdvanced = type.includes("advanced"); + + if (passwordInputWrapper) { + if (isAdvanced) { + passwordInputWrapper.classList.remove("hidden"); + } else { + passwordInputWrapper.classList.add("hidden"); + } + } + + if (fileSection) { + if (isAdvanced) { + fileSection.classList.remove("hidden"); + } else { + fileSection.classList.add("hidden"); + } + } + + updateToggleLabels(); + toggleInputMode(); +} + +function updateToggleLabels() { + const type = document.getElementById("encryption-type")?.value; + const leftLabel = document.getElementById("toggle-left-label"); + const rightLabel = document.getElementById("toggle-right-label"); + + if (!type || !leftLabel || !rightLabel) return; + + const isAdvanced = type.toLowerCase().includes("advanced"); + leftLabel.textContent = isAdvanced ? "Encrypt" : "Encode"; + rightLabel.textContent = isAdvanced ? "Decrypt" : "Decode"; +} + +function toggleInputMode() { + const fileInput = document.getElementById("file-input"); + const textValue = document.getElementById("input-text")?.value.trim(); + const isAdvanced = document.getElementById("encryption-type")?.value === "advanced"; + + const textSection = document.getElementById("text-section"); + const fileSection = document.getElementById("file-section"); + const removeBtn = document.getElementById("remove-file-btn"); + + if (!fileInput || !textSection || !fileSection || !removeBtn) return; + + const fileSelected = fileInput.files.length > 0; + + textSection.style.display = fileSelected ? "none" : "flex"; + fileSection.style.display = (isAdvanced && !textValue) ? "flex" : "none"; + removeBtn.style.display = fileSelected ? "inline-block" : "none"; +} + +// ===== Form Handling ===== +async function handleSubmit(event) { + event.preventDefault(); + + const encryptionType = document.getElementById("encryption-type")?.value; + const password = document.getElementById("password")?.value; + const fileInput = document.getElementById("file-input"); + const isDecrypt = document.getElementById("operation-toggle").checked; + const operation = isDecrypt ? "decrypt" : "encrypt"; + + if (!encryptionType || !fileInput) return; + + if (encryptionType === "advanced" && !password) { + return alert("Password is required for advanced encryption."); + } + + if (fileInput.files.length > 0) { + return (operation === "encrypt") + ? encryptFile(fileInput, password) + : decryptFile(fileInput, password); + } + + await handleTextOperation(encryptionType, operation, password); +} + +async function handleTextOperation(encryptionType, operation, password) { + const payload = { + "encryption-type": encryptionType, + operation: operation, + message: document.getElementById("input-text")?.value, + password: password + }; + + try { + const response = await fetch("/", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + const data = await response.json(); + document.getElementById("output-text").value = data.result; + } catch (err) { + alert("Error processing request: " + err.message); + } +} + +// ===== Utility Functions ===== +function removeFile() { + const fileInput = document.getElementById("file-input"); + if (fileInput) fileInput.value = ""; + const removeBtn = document.getElementById("remove-file-btn"); + if (removeBtn) removeBtn.style.display = 'none'; + toggleInputMode(); +} + +function generateRandomPassword() { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~"; + const length = 30; + const password = Array.from({ length }, () => + charset.charAt(Math.floor(Math.random() * charset.length)) + ).join(""); + const passwordField = document.getElementById("generated-password"); + if (passwordField) { + passwordField.value = password; + // Check if we should start Pacman + checkForPacman(); + } +} + +function copyToClipboard(elementId, feedbackId) { + const el = document.getElementById(elementId); + const feedback = document.getElementById(feedbackId); + + if (!el || !el.value) return; + + // Create a temporary textarea element + const textarea = document.createElement('textarea'); + textarea.value = el.value; + textarea.style.position = 'fixed'; + textarea.style.opacity = '0'; + document.body.appendChild(textarea); + + // Select and copy the text + textarea.select(); + textarea.setSelectionRange(0, 99999); // For mobile devices + + try { + // Try using the modern clipboard API first + navigator.clipboard.writeText(el.value).then(() => { + showFeedback(feedback); + }).catch(() => { + // Fallback to execCommand for older browsers + document.execCommand('copy'); + showFeedback(feedback); + }); + } catch (err) { + // Final fallback + document.execCommand('copy'); + showFeedback(feedback); + } + + // Clean up + document.body.removeChild(textarea); +} + +function showFeedback(feedback) { + if (feedback) { + feedback.style.display = "block"; + feedback.classList.add("show"); + setTimeout(() => { + feedback.classList.remove("show"); + setTimeout(() => { + feedback.style.display = "none"; + }, 300); + }, 3000); + } +} + +function clearAll() { + const fields = ["input-text", "output-text", "file-input", "password"]; + fields.forEach(id => { + const el = document.getElementById(id); + if (el) el.value = ""; + }); + removeFile(); + toggleInputMode(); + document.getElementById("pacman-section")?.style.setProperty("display", "none"); + document.getElementById("encoding-section")?.style.setProperty("display", "block"); +} + +function handleInputChange() { + toggleInputMode(); + checkForPacman(); +} + +function checkForPacman() { + const val = document.getElementById("input-text").value.trim().toLowerCase(); + const pacSection = document.getElementById("pacman-section"); + const encSection = document.getElementById("encoding-section"); + + if (val.includes("pacman") && pacSection.style.display !== "block") { + pacSection.style.display = "block"; + encSection.style.display = "none"; + window.startPacman(); + } else if (pacSection.style.display === "block" && !val.includes("pacman")) { + window.exitGame(); + } +} +function copyShareLink() { + const linkEl = document.getElementById("share-link"); + const feedback = document.getElementById("shared-link-feedback"); + + if (!linkEl) return; + + const linkText = linkEl.href || linkEl.textContent.trim(); + + if (navigator.clipboard && window.isSecureContext) { + navigator.clipboard.writeText(linkText).then(() => { + showCopyFeedback(feedback); + }).catch(() => { + fallbackCopy(linkText, feedback); + }); + } else { + fallbackCopy(linkText, feedback); + } +} + +function fallbackCopy(text, feedbackEl) { + const tempInput = document.createElement("input"); + tempInput.value = text; + document.body.appendChild(tempInput); + tempInput.select(); + + try { + document.execCommand("copy"); + showCopyFeedback(feedbackEl); + } catch (err) { + alert("Copy failed. Please copy manually."); + } + + document.body.removeChild(tempInput); +} + +function showCopyFeedback(feedbackEl) { + if (!feedbackEl) return; + feedbackEl.style.display = "block"; + feedbackEl.classList.add("show"); + + setTimeout(() => { + feedbackEl.classList.remove("show"); + setTimeout(() => { + feedbackEl.style.display = "none"; + }, 300); + }, 3000); +} + + +function startPacman() { } +function exitGame() { } + + diff --git a/templates/404.html b/templates/404.html index ce6247d..1e453e7 100644 --- a/templates/404.html +++ b/templates/404.html @@ -32,14 +32,14 @@

404 - Not Found

-

+

Whoops! That page doesn't seem to exist. Maybe it got encrypted?

-
diff --git a/templates/500.html b/templates/500.html index e896b8a..783c7f6 100644 --- a/templates/500.html +++ b/templates/500.html @@ -31,16 +31,16 @@
-

💥 500 - Server Error

+

500 - Server Error

- Uh oh! The ghosts chomped the server wires. 🧟‍♂️👾 + Uh oh! The ghosts chomped the server wires. We're working on patching it up.

diff --git a/templates/admin.html b/templates/admin.html index 0b8ce31..d779fcc 100644 --- a/templates/admin.html +++ b/templates/admin.html @@ -53,12 +53,12 @@
- +
- +
- + @@ -88,14 +88,15 @@

Server Status

-
    -
  • Uptime: 0 days, 11 hours, 47 minutes
  • -
  • Server Time: 2025-05-14 14:32:18
  • -
  • Python Version: 3.13.3
  • -
  • Flask Debug Mode: True
  • +
      +
    • Uptime: {{ server_info.uptime }}
    • +
    • Server Time: {{ server_info.server_time }}
    • +
    • Python Version: {{ server_info.python_version }}
    • +
    • Flask Debug Mode: {{ server_info.debug_mode }}
+

Server Logs

diff --git a/templates/admin_login.html b/templates/admin_login.html index 65fe0b5..77984f4 100644 --- a/templates/admin_login.html +++ b/templates/admin_login.html @@ -18,16 +18,20 @@ -
-

PacCrypt Admin

-

Administrator Login

-
- +
+
+ PacCrypt Logo +
+

PACCRYPT

+

Admin Login

+
+
+
-

🔑 Admin Login

+

Admin Login

{% with messages = get_flashed_messages() %} @@ -41,7 +45,7 @@
- +
diff --git a/templates/admin_settings.html b/templates/admin_settings.html index c5b5c71..877048c 100644 --- a/templates/admin_settings.html +++ b/templates/admin_settings.html @@ -16,16 +16,21 @@ -
-

PacCrypt Admin Settings

-

Manage upload configuration

-
- + +
+
+ PacCrypt Logo +
+

PACCRYPT

+

Server Settings

+
+
+
-

⚙️ Upload Settings

+

Upload Settings

{% with messages = get_flashed_messages() %} @@ -51,9 +56,9 @@
- + - +
diff --git a/templates/index.html b/templates/index.html index eeb10c4..8eae4e6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -49,7 +49,7 @@ -
+
@@ -112,9 +112,8 @@
-

PacCrypt Share

-

Securely share encrypted files.

-

Do not lose your passwords, data will be lost forever!

+

PacShare

+

Securely share encrypted files.

{% with messages = get_flashed_messages() %} @@ -126,8 +125,7 @@ {% if "pickup" in message %} {% endif %} @@ -138,6 +136,7 @@ {% endif %} {% endwith %} + - +

BOTH PASSWORDS ARE REQUIRED FOR PICKUP