10 Commits

Author SHA1 Message Date
Tyler 61193320d4 Actually working swipe controls for pacman game 2025-05-01 21:49:06 -10:00
Tyler 1c1fed1dd5 reverting swipe controls 2025-05-01 21:23:13 -10:00
Tyler 1edd1c858c Delete static/js/script.js 2025-05-01 21:20:15 -10:00
Tyler 1d55d4f4ce Swipe controls? 2025-05-01 21:14:34 -10:00
Tyler 7aefd5aff8 small fixes 2025-05-01 20:59:46 -10:00
Tyler 271b4cdc91 Delete restart.bat 2025-05-01 20:59:16 -10:00
Tyler 90dcb7ecb8 small things i forgot on my last push 2025-05-01 19:01:13 -10:00
Tyler 7ec213fad0 V .4.1 2025-05-01 18:46:29 -10:00
Tyler 766386501b Small fixes 2025-04-29 17:43:11 -10:00
Tyler 6ad2b65aba Lots of new features
See release for more info
2025-04-29 16:38:33 -10:00
23 changed files with 3094 additions and 1079 deletions
+177 -172
View File
@@ -1,172 +1,177 @@
# PacCrypt WebApp # PacCrypt WebApp
**PacCrypt** is a web-based platform that allows you to securely encrypt/decrypt text and files, generate passwords, and even enjoy a hidden Pac-Man game! **PacCrypt** is a secure, feature-rich web app for encrypting and decrypting text and files — built with Flask, JavaScript, and AES-GCM encryption.
Built using Python (Flask), JavaScript, and AES-GCM encryption. Now with an admin control panel, GitHub updater, and a built-in Pac-Man easter egg! 🕹️
Official Website: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev) Live demo: [paccrypt.unnaturalll.dev](http://paccrypt.unnaturalll.dev)
--- ---
## ✨ Features ## ✨ Features
- 🔒 **Basic and Advanced Encryption** (Text and Files) - 🔒 Basic and Advanced Encryption for Text & Files
- 🔑 **Password Generator** - 📁 Secure File Uploads with Pickup Passwords
- 🄹️ **Pac-Man Easter Egg** (Type `pacman` to unlock!) - 🔑 Random Password Generator
- 📱 **Responsive Design** (Mobile Friendly) - 🎮 Hidden Pac-Man Game — type `pacman` to play
- **One-Click Start Scripts** (Dev and Production modes) - 🧠 Smart UI: Auto-switches input sections, toggles encryption labels
- 🎨 **Modern Animated UI** (Dark Mode + Green Neon Theme) - 📋 Clipboard Copy Feedback with styled status boxes
- 🧾 Admin Panel:
--- - Site map with live route list
- Server restart & GitHub update button
## 👨‍💻 Installation - Secure admin credential management
- Server logs & upload cleanup
### 📋 Prerequisites - 🧩 System Settings Page for upload config
- 📜 Custom 403, 404, and 500 Error Pages
- **Python 3.7+** - 🤖 robots.txt and /sitemap for crawlers
- **Flask 3+** - 📱 Mobile-Responsive UI
- **Cryptography 42+**
- **Waitress 2.1+** ---
- **Nginx** (Recommended for production)
## 👨‍💻 Installation
---
### 📋 Prerequisites
### ⚡ Quick Setup
- Python 3.7+
1. Clone the repository: - Flask 3+
- Cryptography 42+
```bash - Waitress 2.1+
git clone https://github.com/TySP-Dev/PacCrypt.git - Git (for update feature)
cd paccrypt-webapp-final - Nginx (recommended)
```
---
2. Create and activate a virtual environment:
### ⚡ Quick Setup
```bash
python -m venv venv ```bash
source venv/bin/activate # Windows: venv\Scripts\activate git clone https://github.com/TySP-Dev/PacCrypt.git
``` cd paccrypt-webapp-final
python -m venv venv
3. Install required Python packages: source venv/bin/activate # or venv\Scripts\activate on Windows
pip install -r requirements.txt
```bash ```
pip install -r requirements.txt
``` Then run:
4. Start the app: - Development Mode:
```bash
**Windows**: ./start_dev.sh # or start_dev.bat
```bash ```
start_dev.bat # For Development
start_prod.bat # For Production - Production Mode:
``` ```bash
./start_prod.sh # or start_prod.bat
**Linux / Mac**: ```
```bash
chmod +x start_dev.sh start_prod.sh Visit [http://127.0.0.1:5000](http://127.0.0.1:5000)
./start_dev.sh # For Development
./start_prod.sh # For Production ---
```
## 🧭 Navigation & Usage
5. Access the app at:
[http://127.0.0.1:5000](http://127.0.0.1:5000) ### 🔐 Encrypt & Decrypt
--- - Choose between Basic Cipher or Advanced AES
- Type your message or upload a file
## 🚀 Usage Guide - Enter password (if AES)
- Select mode using toggle (Encrypt/Decrypt)
### 🔒 Text Encryption/Decryption - Hit Execute
- Select **Encryption Type** (Basic or Advanced) ### 📤 Share Files
- Enter text
- Provide password (Advanced only) - Upload a file with two passwords:
- Choose **Encrypt** or **Decrypt** - Encryption password
- Click **Submit** - Pickup password
- Get a shareable URL and click 📋 Copy Link
### 📁 File Encryption/Decryption
### 🔑 Generate Passwords
- Select **Advanced** encryption
- Upload a file - Click Generate
- Provide password - Then hit 📋 Copy
- Choose **Encrypt** or **Decrypt**
- Click **Submit** ### 🎮 Pac-Man Game
### 🔑 Password Generator - Type `pacman` in the input box
- Game appears with Restart/Exit controls
- Click **Generate** to create a secure password - Classic arrow key controls 🕹️
- Click **Copy** to save it to clipboard
---
### 🎮 Pac-Man Easter Egg
## 🛠️ Admin Panel
- Type **`pacman`** into the input box to unlock the hidden Pac-Man game!
Visit `/adminpage` after setting up credentials at `/admin-setup`.
---
Features:
## 🛡️ Hosting with Nginx (optional) - 🔄 Restart server
- 🔃 Update from GitHub (git pull)
Recommended for secure public deployment. - 🧽 Clear uploads
- 🔐 Change admin password
Example minimal Nginx config: - 📝 View logs
- ⚙️ Adjust upload settings
```nginx
server { ---
listen 80;
server_name yourdomain.com; ## 🛡️ Deployment Tips
location / { Minimal Nginx config:
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host; ```nginx
proxy_set_header X-Real-IP $remote_addr; server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; listen 80;
} server_name yourdomain.com;
}
``` location / {
proxy_pass http://127.0.0.1:5000;
> Tip: Set up SSL with Let's Encrypt for HTTPS security! 🔐 proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
--- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
## 📂 Project Structure }
```
```
paccrypt-webapp-final/ Use Let's Encrypt to add SSL/TLS support.
├── app.py
├── requirements.txt ---
├── templates/
│ ├── index.html ## 🗂️ Project Structure
│ ├── 404.html
│ └── 403.html ```
│ └── 500.html paccrypt-webapp-final/
├── static/ ├── app.py
│ ├── css/ ├── requirements.txt
│ │ └── styles.css ├── README.md
│ ├── js/ ├── templates/
│ └── script.js ├── index.html
│ ├── img/ │ ├── 404.html
│ └── PacCrypt.png └── 403.html
│ └── audio/ │ └── 500.html
└── chomp.mp3 └── admin.html
├── start_dev.bat │ └── admin_login.html
├── start_prod.bat │ └── admin_settings.html
├── start_dev.sh │ └── admin_setup.html
├── start_prod.sh │ └── pickup.html
├── README.md ├── static/
``` │ ├── css/
│ │ └── styles.css
--- │ ├── js/
│ │ └── ui.js
## 🤝 Contributing │ │ └── pacman.js
│ │ └── main.js
Contributions are welcome! │ │ └── fileops.js
│ │ └── encryption.js
- Add new features │ ├── img/
- Fix bugs │ │ └── PacCrypt.png
- Improve performance │ │ └── Github_logo.png
- Expand the Pac-Man Easter Egg 🎮 │ │ └── sitemap.png
│ └── audio/
--- │ └── chomp.mp3
├── start_dev.bat
## 📄 License ├── start_prod.bat
├── start_dev.sh
This project is licensed under the **MIT License**. ├── start_prod.sh
```
---
---
## 📄 License
MIT © [TySP-Dev](https://github.com/TySP-Dev)
+720 -112
View File
@@ -1,112 +1,720 @@
## DEV DEV DEV # ===== Standard Library Imports =====
import os
import os import io
from flask import Flask, render_template, request, jsonify import json
import html import html
import base64 import base64
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC import hashlib
from cryptography.hazmat.primitives.hashes import SHA256 import secrets
from cryptography.hazmat.primitives.ciphers.aead import AESGCM import datetime
import subprocess
app = Flask(__name__) import platform
from datetime import UTC
# ====== Your App Code ====== import sys
ALPHABET = list('abcdefghijklmnopqrstuvwxyz') # ===== Third-Party Imports =====
from flask import (
def simple_encode(text: str) -> str: Flask, render_template, request, jsonify, session,
return ''.join( redirect, url_for, flash, send_file
ALPHABET[(ALPHABET.index(c) + 3) % 26] if c in ALPHABET else c )
for c in text.lower() from werkzeug.utils import secure_filename
) from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.primitives.hashes import SHA256
def simple_decode(text: str) -> str: from cryptography.hazmat.primitives.ciphers.aead import AESGCM
return ''.join( from cryptography.fernet import Fernet
ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c
for c in text.lower() # ===== Application Configuration =====
) app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET", os.urandom(24))
def derive_key(password: str, salt: bytes) -> bytes:
kdf = PBKDF2HMAC( # ===== Constants =====
algorithm=SHA256(), ADMIN_CRED_FILE = 'admin_creds.json'
length=32, ADMIN_KEY_FILE = 'admin_key.key'
salt=salt, ADMIN_LOG_FILE = 'admin_logs.enc'
iterations=200_000, SETTINGS_FILE = 'settings.json'
) ALPHABET = list('abcdefghijklmnopqrstuvwxyz')
return kdf.derive(password.encode())
DEFAULT_SETTINGS = {
def advanced_encrypt(plaintext: str, password: str) -> str: "upload_folder": "uploads",
salt = os.urandom(16) "max_file_age_days": 14,
key = derive_key(password, salt) "max_file_size_bytes": 25 * 1024 * 1024 * 1024 # 25GB
}
aesgcm = AESGCM(key)
nonce = os.urandom(12) # ===== Settings Management =====
def load_settings():
ct = aesgcm.encrypt(nonce, plaintext.encode(), None) """Load application settings from file or create with defaults."""
encrypted = salt + nonce + ct if not os.path.exists(SETTINGS_FILE):
return base64.urlsafe_b64encode(encrypted).decode() with open(SETTINGS_FILE, 'w') as f:
json.dump(DEFAULT_SETTINGS, f)
def advanced_decrypt(token_b64: str, password: str) -> str: with open(SETTINGS_FILE, 'r') as f:
try: return json.load(f)
data = base64.urlsafe_b64decode(token_b64.encode())
salt, nonce, ct = data[:16], data[16:28], data[28:] settings = load_settings()
key = derive_key(password, salt) UPLOAD_FOLDER = settings["upload_folder"]
aesgcm = AESGCM(key) MAX_FILE_AGE_DAYS = settings["max_file_age_days"]
pt = aesgcm.decrypt(nonce, ct, None) MAX_FILE_SIZE_BYTES = settings["max_file_size_bytes"]
return pt.decode()
except Exception: # Ensure upload folder exists and has proper permissions
return "[Error] Invalid password or corrupted data!" if not os.path.exists(UPLOAD_FOLDER):
try:
@app.route("/", methods=["GET", "POST"]) os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def index(): # Set permissions to 755 (rwxr-xr-x)
if request.method == 'POST': os.chmod(UPLOAD_FOLDER, 0o755)
data = request.get_json() print(f"[INFO] Created upload directory: {UPLOAD_FOLDER}")
encryption_type = data.get("encryption-type", "basic") except Exception as e:
operation = data.get("operation", "") print(f"[ERROR] Failed to create upload directory: {str(e)}")
message = data.get("message", "") raise
password = data.get("password", "")
file_password = data.get("file-password", "") # ===== Cryptographic Functions =====
def derive_key(password: str, salt: bytes) -> bytes:
final_password = file_password if file_password else password """Derive a cryptographic key from password using PBKDF2."""
return PBKDF2HMAC(algorithm=SHA256(), length=32, salt=salt, iterations=200_000).derive(password.encode())
if encryption_type == "basic":
result = simple_encode(message) if operation == "encrypt" else simple_decode(message) def hash_password(password: str, salt: bytes) -> str:
else: """Hash a password with salt for secure storage."""
result = advanced_encrypt(message, final_password) if operation == "encrypt" else advanced_decrypt(message, final_password) return base64.urlsafe_b64encode(derive_key(password, salt)).decode()
return jsonify(result=html.escape(result)) def simple_encode(text: str) -> str:
"""Basic Caesar cipher encryption."""
return render_template( return ''.join(ALPHABET[(ALPHABET.index(c) + 3) % 26] if c in ALPHABET else c for c in text.lower())
"index.html",
result="", def simple_decode(text: str) -> str:
password="", """Basic Caesar cipher decryption."""
encryption_type="advanced" return ''.join(ALPHABET[(ALPHABET.index(c) - 3) % 26] if c in ALPHABET else c for c in text.lower())
)
def advanced_encrypt(plaintext: str, password: str) -> str:
# ====== Smart Server Startup ====== """Encrypt text using AES-GCM with password-derived key."""
salt = os.urandom(16)
@app.errorhandler(404) key = derive_key(password, salt)
def page_not_found(e): nonce = os.urandom(12)
return render_template('404.html'), 404 ct = AESGCM(key).encrypt(nonce, plaintext.encode(), None)
return base64.urlsafe_b64encode(salt + nonce + ct).decode()
@app.errorhandler(500)
def server_error(e): def advanced_decrypt(token_b64: str, password: str) -> str:
return render_template('500.html'), 500 """Decrypt text using AES-GCM with password-derived key."""
try:
@app.errorhandler(403) data = base64.urlsafe_b64decode(token_b64.encode())
def forbidden(e): salt, nonce, ct = data[:16], data[16:28], data[28:]
return render_template('403.html'), 403 key = derive_key(password, salt)
return AESGCM(key).decrypt(nonce, ct, None).decode()
if __name__ == "__main__": except Exception:
PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true" return "[Error] Invalid password or corrupted data!"
if PRODUCTION: # ===== Admin Authentication =====
from waitress import serve def load_admin_key():
print("[INFO] Running in PRODUCTION mode with Waitress.") """Load or generate admin encryption key."""
serve(app, host="0.0.0.0", port=5000) if not os.path.exists(ADMIN_KEY_FILE):
else: with open(ADMIN_KEY_FILE, 'wb') as f:
print("[INFO] Running in DEVELOPMENT mode with Flask server.") f.write(Fernet.generate_key())
app.run(debug=True, host="0.0.0.0", port=5000) with open(ADMIN_KEY_FILE, 'rb') as f:
return f.read()
## DEV DEV DEV def encrypt_creds(username, password):
"""Encrypt and store admin credentials."""
key = load_admin_key()
cipher = Fernet(key)
salt = os.urandom(16)
hashed_pw = hash_password(password, salt)
data = json.dumps({"u": username, "p": hashed_pw, "s": base64.b64encode(salt).decode()}).encode()
with open(ADMIN_CRED_FILE, 'wb') as f:
f.write(cipher.encrypt(data))
def check_creds(username, password):
"""Verify admin credentials."""
try:
key = load_admin_key()
cipher = Fernet(key)
with open(ADMIN_CRED_FILE, 'rb') as f:
decrypted = cipher.decrypt(f.read())
creds = json.loads(decrypted)
salt = base64.b64decode(creds["s"])
return creds["u"] == username and creds["p"] == hash_password(password, salt)
except Exception as e:
print("[ERROR] check_creds failed:", e)
return False
def log_admin_event(message: str):
"""Log admin actions securely."""
try:
key = load_admin_key()
cipher = Fernet(key)
timestamp = datetime.datetime.now(UTC).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")
except Exception as e:
print("[ERROR] Failed to write admin log:", e)
# ===== File Management =====
def cleanup_expired_files():
"""Remove files older than MAX_FILE_AGE_DAYS."""
try:
now = datetime.datetime.now(UTC)
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)
age = (now - file_time).days
if age > MAX_FILE_AGE_DAYS:
os.remove(path)
print(f"[INFO] Deleted expired file: {fname}")
except Exception as e:
print(f"[ERROR] Could not check/delete file {fname}: {e}")
except Exception as e:
print(f"[ERROR] Failed to cleanup expired files: {str(e)}")
# ===== Route Handlers =====
@app.route("/", methods=["GET", "POST"])
def index():
"""Main application route handling encryption/decryption and file uploads."""
if request.method == 'POST':
if 'file' in request.files:
return handle_file_upload(request)
else:
return handle_text_operation(request)
return render_template("index.html", result="", password="", encryption_type="advanced", settings=settings)
def handle_file_upload(request):
"""Process file upload and encryption."""
file = request.files['file']
enc_password = request.form.get('enc_password')
pickup_password = request.form.get('pickup_password')
if not file or not enc_password or not pickup_password:
return jsonify({"error": "Missing fields"}), 400
if file.content_length and file.content_length > MAX_FILE_SIZE_BYTES:
return jsonify({"error": f"File too large! Limit: {MAX_FILE_SIZE_BYTES / (1024**3):.2f} GB"}), 400
filename = secure_filename(file.filename)
temp_path = os.path.join(UPLOAD_FOLDER, filename)
file.save(temp_path)
with open(temp_path, 'rb') as f:
data = f.read()
salt = os.urandom(16)
key = derive_key(enc_password, salt)
nonce = os.urandom(12)
ct = AESGCM(key).encrypt(nonce, data, None)
random_id = secrets.token_urlsafe(24)
with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.enc"), 'wb') as f:
f.write(salt + nonce + ct)
os.remove(temp_path)
meta = {
'pickup_password': base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode(),
'original_name': filename,
'timestamp': datetime.datetime.now(UTC).isoformat()
}
with open(os.path.join(UPLOAD_FOLDER, f"{random_id}.json"), 'w') as f:
json.dump(meta, f)
pickup_url = request.host_url.rstrip('/') + url_for('pickup_file', file_id=random_id)
return jsonify({"success": True, "pickup_url": pickup_url})
def handle_text_operation(request):
"""Process text encryption/decryption operations."""
data = request.get_json()
encryption_type = data.get("encryption-type", "basic")
operation = data.get("operation", "")
message = data.get("message", "")
password = data.get("password", "")
if encryption_type == "basic":
result = simple_encode(message) if operation == "encrypt" else simple_decode(message)
else:
result = advanced_encrypt(message, password) if operation == "encrypt" else advanced_decrypt(message, password)
return jsonify(result=html.escape(result))
# ===== File Pickup Route =====
@app.route("/pickup/<file_id>", methods=["GET", "POST"])
def pickup_file(file_id):
"""Handle file pickup and decryption."""
meta_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.json")
enc_path = os.path.join(UPLOAD_FOLDER, f"{file_id}.enc")
if not os.path.exists(meta_path) or not os.path.exists(enc_path):
flash("File not found or expired")
return redirect(url_for('index'))
if request.method == 'POST':
return handle_file_pickup(request, meta_path, enc_path, file_id)
return render_template("pickup.html", file_id=file_id)
def handle_file_pickup(request, meta_path, enc_path, file_id):
"""Process file pickup and decryption."""
pickup_password = request.form.get('pickup_password')
enc_password = request.form.get('enc_password')
if not pickup_password or not enc_password:
flash("Missing fields")
return redirect(request.url)
with open(meta_path, 'r') as f:
meta = json.load(f)
expected_hash = base64.urlsafe_b64encode(hashlib.sha256(pickup_password.encode()).digest()).decode()
if expected_hash != meta['pickup_password']:
flash("Incorrect pickup password")
return redirect(request.url)
with open(enc_path, 'rb') as f:
enc_data = f.read()
salt, nonce, ct = enc_data[:16], enc_data[16:28], enc_data[28:]
key = derive_key(enc_password, salt)
try:
decrypted = AESGCM(key).decrypt(nonce, ct, None)
except Exception:
flash("Decryption failed")
return redirect(request.url)
os.remove(meta_path)
os.remove(enc_path)
log_admin_event(f"File {file_id} downloaded and deleted.")
response = send_file(
io.BytesIO(decrypted),
as_attachment=True,
download_name=meta['original_name'],
mimetype='application/octet-stream'
)
# Add headers for better mobile compatibility
response.headers['Content-Disposition'] = f'attachment; filename="{meta["original_name"]}"'
response.headers['Content-Type'] = 'application/octet-stream'
response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
# ===== Admin Routes =====
@app.route("/admin-logs")
def admin_logs():
"""View admin activity logs."""
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
logs = []
try:
key = load_admin_key()
cipher = Fernet(key)
if os.path.exists(ADMIN_LOG_FILE):
with open(ADMIN_LOG_FILE, 'rb') as f:
lines = f.readlines()
for line in lines[-100:]:
if line.strip():
try:
decrypted = cipher.decrypt(line.strip())
logs.append(decrypted.decode())
except Exception:
logs.append("[Error] Corrupted log entry.")
except Exception as e:
logs.append(f"[Error loading logs] {str(e)}")
return jsonify(logs=logs)
@app.route("/admin-settings", methods=["GET", "POST"])
def admin_settings():
"""Manage application settings."""
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
current_settings = load_settings()
if request.method == 'POST':
return handle_settings_update(request, current_settings)
return render_template("admin_settings.html", settings=current_settings)
def handle_settings_update(request, current_settings):
"""Process settings update request."""
upload_folder = request.form.get('upload_folder', current_settings.get('upload_folder', 'uploads'))
max_file_age_days = int(request.form.get('max_file_age_days', current_settings.get('max_file_age_days', 14)))
max_file_size_gb = float(request.form.get('max_file_size_gb', current_settings.get('max_file_size_bytes', 25 * 1024 * 1024 * 1024) / (1024 * 1024 * 1024)))
max_file_size_bytes = int(max_file_size_gb * 1024 * 1024 * 1024)
updated_settings = {
"upload_folder": upload_folder,
"max_file_age_days": max_file_age_days,
"max_file_size_bytes": max_file_size_bytes
}
with open(SETTINGS_FILE, 'w') as f:
json.dump(updated_settings, f)
flash("Settings updated successfully!")
global settings, UPLOAD_FOLDER, MAX_FILE_AGE_DAYS, MAX_FILE_SIZE_BYTES
settings = load_settings()
UPLOAD_FOLDER = settings.get('upload_folder', 'uploads')
MAX_FILE_AGE_DAYS = settings.get('max_file_age_days', 14)
MAX_FILE_SIZE_BYTES = settings.get('max_file_size_bytes', 25 * 1024 * 1024 * 1024)
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
return redirect(url_for("admin_settings"))
@app.route("/admin-setup", methods=["GET", "POST"])
def admin_setup():
"""Initial admin account setup."""
if os.path.exists(ADMIN_CRED_FILE):
return redirect(url_for("admin_login"))
if request.method == "POST":
u = request.form.get("username")
p = request.form.get("password")
if u and p:
encrypt_creds(u, p)
session["admin_logged_in"] = True
return redirect(url_for("admin_page"))
flash("Both fields required")
return render_template("admin_setup.html")
@app.route("/admin-login", methods=["GET", "POST"])
def admin_login():
"""Admin login handler."""
if request.method == "POST":
u = request.form.get("username")
p = request.form.get("password")
if check_creds(u, p):
session["admin_logged_in"] = True
log_admin_event("Admin login successful.")
return redirect(url_for("admin_page"))
else:
log_admin_event("Admin login failed.")
flash("Incorrect credentials")
return render_template("admin_login.html")
@app.route("/admin-logout")
def admin_logout():
"""Admin logout handler."""
session.pop("admin_logged_in", None)
return redirect(url_for("index"))
@app.route("/adminpage")
def admin_page():
"""Admin dashboard."""
if not session.get("admin_logged_in"):
if not os.path.exists(ADMIN_CRED_FILE):
return redirect(url_for("admin_setup"))
return redirect(url_for("admin_login"))
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"
server_info = {
"uptime": uptime_str,
"time": datetime.datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S"),
"python": platform.python_version(),
"debug": 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."""
if not session.get("admin_logged_in"):
return jsonify({"error": "Unauthorized"}), 401
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
taskkill /F /PID {current_pid}
set PRODUCTION=true
start "" "python" "app.py"
"""
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
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
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)
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
@app.route("/admin-reset", methods=["POST"])
def admin_reset():
"""Reset admin credentials."""
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
try:
if os.path.exists(ADMIN_CRED_FILE):
os.remove(ADMIN_CRED_FILE)
if os.path.exists(ADMIN_KEY_FILE):
os.remove(ADMIN_KEY_FILE)
session.pop("admin_logged_in", None)
flash("Admin credentials reset. Please create new credentials.")
except Exception as e:
flash("Failed to reset admin credentials.")
print("[ERROR] admin_reset failed:", e)
return redirect(url_for("admin_setup"))
@app.route("/admin-change-password", methods=["POST"])
def admin_change_password():
"""Change admin password."""
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
current = request.form.get("current_password")
new = request.form.get("new_password")
try:
key = load_admin_key()
cipher = Fernet(key)
with open(ADMIN_CRED_FILE, 'rb') as file:
decrypted = cipher.decrypt(file.read())
creds = json.loads(decrypted)
salt = base64.b64decode(creds["s"])
if hash_password(current, salt) != creds["p"]:
flash("Current password is incorrect")
return redirect(url_for("admin_page"))
creds["p"] = hash_password(new, salt)
encrypted = cipher.encrypt(json.dumps(creds).encode())
with open(ADMIN_CRED_FILE, 'wb') as file:
file.write(encrypted)
log_admin_event("Admin password changed.")
flash("Password updated successfully", "password-feedback")
return redirect(url_for("admin_page"))
except Exception as e:
flash("Failed to update password")
print("[ERROR] Password change failed:", e)
return redirect(url_for("admin_page"))
@app.route("/admin-clear-uploads", methods=["POST"])
def admin_clear_uploads():
"""Clear all uploaded files."""
if not session.get("admin_logged_in"):
return redirect(url_for("admin_login"))
deleted = 0
for filename in os.listdir(UPLOAD_FOLDER):
if filename.endswith(".enc") or filename.endswith(".json"):
try:
os.remove(os.path.join(UPLOAD_FOLDER, filename))
deleted += 1
except Exception as e:
print("[ERROR] Failed to delete:", filename, e)
flash(f"Cleared {deleted} uploaded file(s).", "clear-feedback")
return redirect(url_for("admin_page"))
@app.route("/admin-update-server", methods=["POST"])
def admin_update_server():
"""Update server from GitHub repository."""
if not session.get("admin_logged_in"):
return jsonify({"error": "Unauthorized"}), 401
try:
# Get the absolute path of the current directory
current_dir = os.path.abspath(os.path.dirname(__file__))
# Try to find git executable
git_paths = [
"/usr/bin/git", # Standard Debian path
"/usr/local/bin/git",
"/bin/git",
"git" # Fallback to PATH
]
git_cmd = None
for path in git_paths:
if os.path.exists(path) or path == "git":
try:
# Test if git is executable
subprocess.run([path, "--version"], check=True, capture_output=True)
git_cmd = path
break
except Exception:
continue
if not git_cmd:
return jsonify({"error": "Git executable not found. Please ensure git is installed and accessible."}), 500
# Try to find the git repository by checking parent directories
repo_dir = current_dir
max_depth = 5 # Limit how far up we'll look
found_git = False
for _ in range(max_depth):
git_dir = os.path.join(repo_dir, ".git")
if os.path.exists(git_dir):
found_git = True
break
parent_dir = os.path.dirname(repo_dir)
if parent_dir == repo_dir: # We've reached the root directory
break
repo_dir = parent_dir
if not found_git:
return jsonify({
"error": "Git repository not found. Current directory: " + current_dir,
"details": "Please ensure the application is running from within the git repository directory."
}), 400
# Execute git commands with proper error handling
try:
# Fetch latest changes
fetch_result = subprocess.run([git_cmd, "fetch"], cwd=repo_dir, check=True, capture_output=True, text=True)
# Reset to origin/main
reset_result = subprocess.run([git_cmd, "reset", "--hard", "origin/main"], cwd=repo_dir, check=True, capture_output=True, text=True)
# Pull latest changes
pull_result = subprocess.run([git_cmd, "pull"], cwd=repo_dir, check=True, capture_output=True, text=True)
return jsonify({
"message": "Server updated successfully from GitHub!",
"details": {
"fetch": fetch_result.stdout,
"reset": reset_result.stdout,
"pull": pull_result.stdout
}
}), 200
except subprocess.CalledProcessError as e:
error_msg = f"Git operation failed: {e.stderr if e.stderr else e.stdout}"
print(f"[ERROR] {error_msg}")
return jsonify({"error": error_msg}), 500
except Exception as e:
error_msg = f"Update failed: {str(e)}"
print(f"[ERROR] {error_msg}")
return jsonify({"error": error_msg}), 500
# ===== Sitemap and Robots =====
@app.route("/sitemap", methods=["GET"])
def sitemap():
"""Generate sitemap.xml."""
sitemap_xml = '''<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url><loc>https://paccrypt.unnaturalll.dev/</loc></url>
<url><loc>https://paccrypt.unnaturalll.dev/pickup</loc></url>
<url><loc>https://paccrypt.unnaturalll.dev/adminpage</loc></url>
<url><loc>https://paccrypt.unnaturalll.dev/sitemap</loc></url>
</urlset>'''
return sitemap_xml, 200, {'Content-Type': 'application/xml'}
@app.route("/robots.txt")
def robots_txt():
"""Generate robots.txt."""
lines = [
"User-agent: *",
"Disallow: /adminpage",
"Disallow: /admin-login",
"Disallow: /admin-setup",
"Disallow: /admin-reset",
"Disallow: /admin-settings",
"Disallow: /restart-server",
"Disallow: /pickup",
"Disallow: /admin-change-password",
"Allow: /",
f"Sitemap: {url_for('sitemap', _external=True)}"
]
return "\n".join(lines), 200, {"Content-Type": "text/plain"}
# ===== Error Handlers =====
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def server_error(e):
return render_template('500.html'), 500
@app.errorhandler(403)
def forbidden(e):
return render_template('403.html'), 403
@app.errorhandler(405)
def method_not_allowed(e):
return render_template('403.html'), 403
@app.errorhandler(FileNotFoundError)
def handle_file_not_found(e):
if os.getenv("PRODUCTION", "false").lower() == "true":
return render_template('500.html'), 500
else:
raise e
# ===== Application Entry Point =====
if __name__ == "__main__":
PRODUCTION = os.getenv("PRODUCTION", "false").lower() == "true"
if PRODUCTION:
from waitress import serve
print("[INFO] Running in PRODUCTION mode with Waitress.")
serve(app, host="0.0.0.0", port=5000)
else:
print("[INFO] Running in DEVELOPMENT mode with Flask server.")
app.run(debug=True, host="0.0.0.0", port=5000)
+8 -7
View File
@@ -1,8 +1,9 @@
### **requirements.txt** ### **requirements.txt**
flask==3.0.3 flask==3.0.3
cryptography==42.0.5 cryptography==42.0.5
waitress==2.1.2 waitress==2.1.2
werkzeug==3.0.1
# nginx - Only needed for Nginx integration, not installed via pip
# nginx - Only needed for Nginx integration, not installed via pip
# Run pip install -r requirements.txt # Run pip install -r requirements.txt
+1
View File
@@ -0,0 +1 @@
{"upload_folder": "uploads", "max_file_age_days": 14, "max_file_size_bytes": 26843545600}
+411 -96
View File
@@ -1,7 +1,7 @@
/* ===== Global Reset ===== */ /* ===== Global Reset ===== */
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 3px;
padding: 0; padding: 0;
} }
@@ -27,7 +27,7 @@ header {
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4); box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
margin-bottom: 30px; margin-bottom: 40px !important;
} }
header h1 { header h1 {
@@ -38,7 +38,6 @@ header {
header p { header p {
font-size: 1.2em; font-size: 1.2em;
color: #00ff99;
} }
/* ===== Main Layout ===== */ /* ===== Main Layout ===== */
@@ -49,30 +48,31 @@ main {
align-items: center; align-items: center;
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
padding: 20px; padding: 0;
gap: 30px; gap: 0;
} }
/* ===== Section Card Styling ===== */ /* ===== Card Styling ===== */
.card { .card {
background-color: #1e1e1e; background-color: #1e1e1e;
padding: 25px; padding: 25px;
width: 100%; width: 100%;
max-width: 800px;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4); box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
text-align: center; text-align: center;
} }
/* ===== Uniform Form Inputs ===== */ /* ===== Form Group Styling ===== */
.form-group { .form-group {
display: flex; display: flex !important;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 20px; gap: 0px;
max-width: 725px;
width: 100%; width: 100%;
} }
/* ===== Inputs, Textareas, Selects ===== */
input, input,
textarea, textarea,
select, select,
@@ -80,28 +80,31 @@ input[type="file"] {
width: 80%; width: 80%;
max-width: 500px; max-width: 500px;
padding: 12px 20px; padding: 12px 20px;
border: 2px solid #00ff99; border: 1px solid #00ff99;
border-radius: 8px; border-radius: 8px;
background-color: #2c2f33; background-color: #2c2f33;
color: #00ff99; color: #00ff99;
font-size: 1em; font-size: 1em;
text-align: left;
transition: 0.3s; transition: 0.3s;
} }
select {
text-align: center;
}
textarea { textarea {
min-height: 140px; min-height: 140px;
resize: none; resize: none;
} }
input[type="password"], input[type="password"] {
#password {
min-height: 50px; min-height: 50px;
} }
input[type="file"] { input[type="file"] {
border: 2px dashed #00ff99; border: 2px dashed #00ff99;
cursor: pointer; cursor: pointer;
text-align: center;
} }
input[type="file"]::file-selector-button { input[type="file"]::file-selector-button {
@@ -118,114 +121,141 @@ input[type="file"] {
background-color: #00cc77; background-color: #00cc77;
} }
/* ===== Focus Effects ===== */
input:focus, input:focus,
textarea:focus, textarea:focus,
select:focus { select:focus {
outline: none; outline: none;
box-shadow: 0 0 8px rgba(0, 255, 153, 0.8); box-shadow: 0 0 10px rgba(0, 255, 153, 0.8);
} }
/* ===== Match input and output sizes ===== */ /* ===== Textareas Specific Widths ===== */
#input-text, #input-text,
#output-text { #output-text {
width: 80%; width: 80%;
max-width: 500px; max-width: 500px;
height: 140px; height: 140px;
box-sizing: border-box;
resize: none;
} }
/* ===== Buttons ===== */ /* ===== Button Group Styling ===== */
.button-group { .button-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: nowrap;
justify-content: center; justify-content: center;
gap: 15px; gap: 15px;
margin-top: 15px;
width: 100%; width: 100%;
} }
button { button {
padding: 10px 20px; padding: 10px 20px;
border: 2px solid #00ff99; border: 0px solid #00ff99;
border-radius: 8px; border-radius: 8px;
background-color: #2c2f33; background-color: #2c2f33;
color: #00ff99; color: #00ff99;
font-size: 1em; font-size: 1em;
cursor: pointer; cursor: pointer;
transition: 0.3s; transition: 0.3s;
width: 100%; width: auto;
max-width: 200px; min-width: 225px;
max-width: 300px;
} }
button:hover { button:hover {
background-color: #00ff99; background-color: #00ff99;
color: #121212; color: #121212;
box-shadow: 0 0 10px rgba(0, 255, 153, 0.4);
} }
/* ===== Toggle Buttons (Encode/Decode, Encrypt/Decrypt) ===== */ .danger-button {
.radio-group { background-color: #5f3131;
box-shadow: 0 0 20px rgba(185, 0, 0, 0.4);
}
.danger-button:hover {
background-color: #ff0000;
color: #121212;
box-shadow: 0 0 40px rgb(255, 0, 0);
}
/* ===== Toggle Switch Styling ===== */
.toggle-container {
display: flex; display: flex;
align-items: center;
justify-content: center; justify-content: center;
gap: 8px; gap: 12px;
margin-top: 8px;
width: 100%; width: 100%;
} }
.radio-button { /* Make sure the switch aligns well */
display: inline-flex; .switch {
align-items: center;
justify-content: center;
padding: 1px 1px;
border: 2px solid #00ff99;
border-radius: 8px;
background-color: #2c2f33;
color: #00ff99;
cursor: pointer;
transition: 0.3s;
position: relative; position: relative;
box-shadow: none; display: flex;
align-items: center; /* <-- Ensures vertical centering */
justify-content: center;
width: 70px;
height: 34px;
} }
.radio-button:hover { /* Hide the checkbox */
background-color: #00ff99; .switch input {
color: #121212; opacity: 0;
width: 0;
height: 0;
} }
/* Hide the actual radio input */ /* The slider */
.radio-button input { .slider {
display: none; position: absolute;
} top: 0;
left: 0;
/* When selected, make the ENTIRE BUTTON glow */ right: 0;
.radio-button input:checked + span { bottom: 0;
background-color: #2c2f33;
color: #00ff99;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.7);
border-radius: 8px;
padding: 8px 18px;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* ===== Remove File Button ===== */
#remove-file-btn {
display: none;
margin-top: 8px;
padding: 8px 16px;
border: 2px solid #ff5555;
background-color: #2c2f33; background-color: #2c2f33;
color: #ff5555; border: 2px solid #00ff99;
border-radius: 8px; border-radius: 34px;
cursor: pointer; transition: .4s;
transition: 0.3s; display: flex;
align-items: center;
} }
#remove-file-btn:hover { /* The circle knob */
background-color: #ff5555; .slider::before {
color: #2c2f33; content: "";
height: 22px;
width: 22px;
background-color: #00ff99;
border-radius: 50%;
transition: .4s;
transform: translateX(2px);
position: absolute;
left: auto;
bottom: auto;
}
input:checked + .slider::before {
transform: translateX(36px);
}
/* Toggle Labels */
.labels {
position: relative;
width: 100px;
display: flex;
justify-content: space-between;
font-size: 0.9em;
color: #00ff99;
margin-top: 5px;
}
.labels::before,
.labels::after {
content: attr(data-on);
width: 50%;
text-align: center;
}
.labels::after {
content: attr(data-off);
} }
/* ===== Toast Notifications ===== */ /* ===== Toast Notifications ===== */
@@ -244,11 +274,11 @@ button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
} }
.toast.show { .toast.show {
visibility: visible; visibility: visible;
animation: fadein 0.5s, fadeout 0.5s 2.5s;
} }
@keyframes fadein { @keyframes fadein {
@@ -271,21 +301,6 @@ button {
} }
} }
/* ===== Pacman Canvas ===== */
.pacman-wrapper {
display: flex;
justify-content: center;
margin-bottom: 18px;
}
#pacmanCanvas {
background-color: black;
border: 2px solid #00ff99;
border-radius: 10px;
width: 800px;
height: 600px;
}
/* ===== Footer ===== */ /* ===== Footer ===== */
footer { footer {
text-align: center; text-align: center;
@@ -294,9 +309,9 @@ footer {
color: #00ff99; color: #00ff99;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4); box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
margin-top: 30px;
width: 100%; width: 100%;
max-width: 800px; max-width: 800px;
margin-top: 40px;
} }
footer a { footer a {
@@ -308,17 +323,317 @@ footer {
color: #ff0066; color: #ff0066;
} }
/* ===== Responsive Tweaks ===== */ /* ===== Responsive Design ===== */
@media (max-width: 600px) { @media (max-width: 600px) {
input, input,
textarea, textarea,
select, select,
#input-text, #input-text,
#output-text, #output-text {
#password-field,
#password,
#file-password {
width: 100%; width: 100%;
max-width: 90%; max-width: 90%;
} }
} }
/* ===== Copy Feedback Message ===== */
.copy-feedback, #shared-link-feedback {
background-color: #2c2f33;
padding: 6px 12px;
margin-top: 6px;
border-radius: 6px;
color: #00ff99;
font-size: 0.9em;
display: none;
opacity: 0;
text-align: center;
max-width: 500px;
margin-left: auto;
margin-right: auto;
transition: opacity 0.3s ease;
}
.copy-feedback.show, #shared-link-feedback.show {
display: block;
opacity: 1;
}
.share-link-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
margin-top: 12px;
margin-bottom: 12px;
}
#share-link {
display: block;
background-color: #2c2f33;
padding: 8px 16px;
border-radius: 6px;
color: #00ff99;
font-size: 0.9em;
text-align: center;
max-width: 720px;
width: 100%;
word-break: break-all;
text-decoration: none;
transition: all 0.3s ease;
}
#share-link:hover {
color: #00cc77;
background-color: #36393f;
}
/* ===== Form Styling ===== */
form {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
form input {
width: 80%;
max-width: 500px;
text-align: left;
}
/* ===== Section Card Styling ===== */
section.card {
display: flex;
flex-direction: column;
align-items: center;
}
/* ===== Pacman Game Styling ===== */
#pacmanCanvas {
background-color: black;
display: block;
border: 2px solid #00ff99;
border-radius: 12px;
max-width: 700px;
width: 100%;
aspect-ratio: 4/3;
object-fit: contain;
}
#pacman-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
margin-bottom: 25px;
max-width: 725px;
width: 100%;
}
.pacman-wrapper {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
padding: 0;
margin: 0;
}
/* ===== Utility Classes ===== */
.hidden {
display: none !important;
}
/* ===== Section Spacing ===== */
#password-generator-section {
margin-bottom: 25px;
}
#encoding-section {
margin-bottom: 25px;
}
/* Pickup page sections */
#pickup-section {
margin-bottom: 25px;
}
#security-notice-section {
margin-bottom: 25px;
}
/* ===== File Input Section ===== */
#encoding-section #file-section {
display: none;
}
#encoding-section #file-section:not(.hidden) {
display: flex;
}
/* Ensure PacCrypt sharing file uploader is always visible */
#sharing-section #file-section {
display: flex;
}
/* Mobile-friendly download button */
.download-btn {
width: 100%;
padding: 12px;
font-size: 16px;
cursor: pointer;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
transition: background-color 0.3s;
}
.download-btn:hover {
background-color: var(--primary-hover);
}
/* Mobile form adjustments */
.pickup-form {
max-width: 100%;
margin: 0 auto;
}
.pickup-form input[type="password"] {
width: 100%;
padding: 12px;
margin-bottom: 10px;
font-size: 16px;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--input-bg);
color: var(--text-color);
}
/* Mobile-specific styles */
@media (max-width: 768px) {
.download-btn {
padding: 15px;
font-size: 18px;
}
.pickup-form input[type="password"] {
padding: 15px;
font-size: 18px;
}
}
/* ===== Admin Section Styling ===== */
#sitemap-section,
#password-change-section,
#server-update-section,
#server-status-section,
#server-logs-section,
#system-settings-section {
margin-bottom: 25px;
padding: 25px;
background-color: #1e1e1e;
border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
}
.sitemap-header {
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px 0;
}
.sitemap-header h3 {
color: #00ff99;
margin: 0;
}
.collapse-btn {
background: none;
border: none;
color: #00ff99;
font-size: 1.2em;
cursor: pointer;
padding: 5px 10px;
transition: transform 0.3s ease;
}
.collapse-btn:hover {
transform: scale(1.1);
}
.sitemap-content {
transition: all 0.3s ease;
margin-bottom: 15px;
}
#sitemap-section ul,
#server-status-section ul {
list-style: none;
padding-left: 0;
margin-top: 15px;
}
#sitemap-section li,
#server-status-section li {
margin-bottom: 10px;
padding: 8px;
background-color: #2c2f33;
border-radius: 6px;
color: #00ff99;
}
#server-logs-section button {
margin-bottom: 15px;
width: 100%;
max-width: 300px;
}
#logLoader {
color: #00ff99;
text-align: center;
padding: 10px;
}
#logContainer {
background-color: #2c2f33;
color: #00ff99;
padding: 15px;
border-radius: 8px;
max-height: 400px;
overflow-y: auto;
font-family: monospace;
white-space: pre-wrap;
}
#system-settings-section {
margin-bottom: unset !important;
padding: 25px;
background-color: #1e1e1e;
border-radius: 12px;
box-shadow: 0 0 15px rgba(0, 255, 153, 0.4);
}
/* ===== 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;
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+104
View File
@@ -0,0 +1,104 @@
/**
* Encryption module.
* Handles cryptographic operations using Web Crypto API.
* Implements AES-GCM encryption with PBKDF2 key derivation.
*/
// ===== Constants =====
const SALT_LENGTH = 16;
const IV_LENGTH = 12;
const PBKDF2_ITERATIONS = 200_000;
const KEY_LENGTH = 256;
// ===== Key Derivation =====
/**
* Derives an AES-GCM key from a password using PBKDF2.
* @param {string} password - User-supplied password.
* @param {Uint8Array} salt - Randomly generated salt.
* @returns {Promise<CryptoKey>} - Derived cryptographic key.
*/
export async function deriveKey(password, salt) {
const encoder = new TextEncoder();
const keyMaterial = await crypto.subtle.importKey(
'raw',
encoder.encode(password),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt,
iterations: PBKDF2_ITERATIONS,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: KEY_LENGTH },
false,
['encrypt', 'decrypt']
);
}
// ===== Encryption =====
/**
* Encrypts a message using AES-GCM with a derived key.
* @param {string} message - Plaintext message to encrypt.
* @param {string} password - User password for key derivation.
* @returns {Promise<string>} - Base64-encoded encrypted string.
*/
export async function encryptAdvanced(message, password) {
const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const key = await deriveKey(password, salt);
const encoded = encoder.encode(message);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoded
);
const output = new Uint8Array(salt.length + iv.length + ciphertext.byteLength);
output.set(salt);
output.set(iv, salt.length);
output.set(new Uint8Array(ciphertext), salt.length + iv.length);
return btoa(String.fromCharCode(...output));
}
// ===== Decryption =====
/**
* Decrypts an AES-GCM encrypted string.
* @param {string} encryptedData - Base64-encoded ciphertext.
* @param {string} password - Password used to derive the decryption key.
* @returns {Promise<string>} - Decrypted plaintext.
*/
export async function decryptAdvanced(encryptedData, password) {
const encrypted = new Uint8Array(
atob(encryptedData).split('').map(c => c.charCodeAt(0))
);
const salt = encrypted.slice(0, SALT_LENGTH);
const iv = encrypted.slice(SALT_LENGTH, SALT_LENGTH + IV_LENGTH);
const ciphertext = encrypted.slice(SALT_LENGTH + IV_LENGTH);
const key = await deriveKey(password, salt);
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
// ===== Module Initialization =====
/**
* Initializes the encryption module and logs its status.
*/
export function setupEncryption() {
console.log('[Encryption] Module loaded');
}
+119
View File
@@ -0,0 +1,119 @@
/**
* File operations module.
* Handles file encryption and decryption operations.
*/
// ===== Constants =====
const CHUNK_SIZE = 1024 * 1024; // 1MB chunks
// ===== Public Interface =====
export async function encryptFile(fileInput, password) {
const file = fileInput.files[0];
if (!file) return;
try {
const encryptedChunks = await processFile(file, password, true);
downloadEncryptedFile(encryptedChunks, file.name);
} catch (error) {
alert("Error encrypting file: " + error.message);
}
}
export async function decryptFile(fileInput, password) {
const file = fileInput.files[0];
if (!file) return;
try {
const decryptedChunks = await processFile(file, password, false);
downloadDecryptedFile(decryptedChunks, file.name);
} catch (error) {
alert("Error decrypting file: " + error.message);
}
}
// ===== File Processing =====
async function processFile(file, password, isEncrypt) {
const chunks = [];
const totalChunks = Math.ceil(file.size / CHUNK_SIZE);
let processedChunks = 0;
for (let start = 0; start < file.size; start += CHUNK_SIZE) {
const chunk = file.slice(start, start + CHUNK_SIZE);
const arrayBuffer = await chunk.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);
const processedChunk = await processChunk(uint8Array, password, isEncrypt);
chunks.push(processedChunk);
processedChunks++;
updateProgress(processedChunks, totalChunks);
}
return chunks;
}
async function processChunk(data, password, isEncrypt) {
const payload = {
"encryption-type": "advanced",
operation: isEncrypt ? "encrypt" : "decrypt",
message: Array.from(data).join(','),
password: password
};
const response = await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
return new Uint8Array(result.result.split(',').map(Number));
}
// ===== File Download =====
function downloadEncryptedFile(chunks, originalName) {
const blob = new Blob(chunks, { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = originalName + '.encrypted';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function downloadDecryptedFile(chunks, originalName) {
const blob = new Blob(chunks, { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = originalName.replace('.encrypted', '');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// ===== Progress Tracking =====
function updateProgress(processed, total) {
const progressBar = document.getElementById("file-progress");
const progressText = document.getElementById("file-progress-text");
if (progressBar && progressText) {
const percent = Math.round((processed / total) * 100);
progressBar.style.width = percent + "%";
progressText.textContent = `Processing: ${percent}%`;
if (processed === total) {
setTimeout(() => {
progressBar.style.width = "0%";
progressText.textContent = "";
}, 1000);
}
}
}
+13
View File
@@ -0,0 +1,13 @@
/**
* Main application entry point.
* Initializes UI and game components when the DOM is loaded.
*/
import { setupUI } from './ui.js';
import { setupGame } from './pacman.js';
// Initialize application when DOM is fully loaded
window.addEventListener("DOMContentLoaded", () => {
setupUI();
setupGame();
});
+376
View File
@@ -0,0 +1,376 @@
/**
* Pacman game module.
* Handles game logic, rendering, and user interaction.
*/
// ===== Game Constants =====
const PACMAN_SPEED = 40;
const ENEMY_SPEED = 20;
const CELL_SIZE = 40;
const DOT_SIZE = 5;
// ===== Game State =====
let canvas, ctx, pacman, enemy, walls, dots, score;
let cols, rows, randSeed, gameInterval;
// ===== Public Interface =====
export function setupGame() {
console.log('[PacMan] Game module loaded.');
window.startPacman = startPacman;
window.exitGame = exitGame;
}
export function startPacman() {
// Scroll to the Pacman section
const pacmanSection = document.getElementById("pacman-section");
if (pacmanSection) {
pacmanSection.scrollIntoView({ behavior: 'smooth' });
}
// Initialize game state
initializeGame();
setupGameLoop();
}
export function stopPacman() {
clearInterval(gameInterval);
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
// Restore scrolling
document.body.style.overflow = '';
document.removeEventListener('wheel', preventScroll);
document.removeEventListener('touchmove', preventScroll);
}
export function resetGame() {
stopPacman();
startPacman();
}
export function exitGame() {
stopPacman();
document.getElementById("input-text").value = "";
document.getElementById("pacman-section").style.display = "none";
document.getElementById("encoding-section").style.display = "block";
}
// ===== Game Initialization =====
function initializeGame() {
canvas = document.getElementById("pacmanCanvas");
ctx = canvas.getContext("2d");
cols = Math.floor(canvas.width / CELL_SIZE);
rows = Math.floor(canvas.height / CELL_SIZE);
walls = [];
dots = [];
score = 0;
clearInterval(gameInterval);
// Get seed from generated password or use default
const passwordField = document.getElementById("generated-password");
const seedSource = passwordField?.value || "pacman";
randSeed = [...seedSource].reduce((s, c) => s + c.charCodeAt(0), 0);
generateWalls();
generateDots();
pacman = spawn();
do { enemy = spawn(); } while (enemy.x === pacman.x && enemy.y === pacman.y);
pacman.dx = pacman.dy = 0;
document.addEventListener("keydown", movePacman);
// Prevent scrolling
document.body.style.overflow = 'hidden';
document.addEventListener('wheel', preventScroll, { passive: false });
document.addEventListener('touchmove', preventScroll, { passive: false });
// Add touch controls
let touchStartX = 0;
let touchStartY = 0;
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
const touchEndX = e.changedTouches[0].clientX;
const touchEndY = e.changedTouches[0].clientY;
const dx = touchEndX - touchStartX;
const dy = touchEndY - touchStartY;
// Determine swipe direction based on the larger movement
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal swipe
if (dx > 0) {
pacman.dx = PACMAN_SPEED;
pacman.dy = 0;
} else {
pacman.dx = -PACMAN_SPEED;
pacman.dy = 0;
}
} else {
// Vertical swipe
if (dy > 0) {
pacman.dx = 0;
pacman.dy = PACMAN_SPEED;
} else {
pacman.dx = 0;
pacman.dy = -PACMAN_SPEED;
}
}
}, { passive: false });
}
function setupGameLoop() {
gameInterval = setInterval(gameLoop, 150);
}
// ===== Game Setup Helpers =====
function spawn() {
const options = [];
for (let c = 1; c < cols - 1; c++) {
for (let r = 1; r < rows - 1; r++) {
if (!walls.some(w => w.c === c && w.r === r)) {
const neighbors = [
{ c: c + 1, r }, { c: c - 1, r },
{ c, r: r + 1 }, { c, r: r - 1 }
];
if (neighbors.some(n => !walls.some(w => w.c === n.c && w.r === n.r))) {
options.push({ c, r });
}
}
}
}
const s = options[Math.floor(rand() * options.length)];
return {
x: s.c * CELL_SIZE + CELL_SIZE / 2,
y: s.r * CELL_SIZE + CELL_SIZE / 2,
size: CELL_SIZE / 2 - 5,
dx: 0,
dy: 0
};
}
function rand() {
const x = Math.sin(randSeed++) * 10000;
return x - Math.floor(x);
}
function generateWalls() {
// First pass: generate initial walls
for (let c = 0; c < cols; c++) {
for (let r = 0; r < rows; r++) {
if (c === 0 || r === 0 || c === cols - 1 || r === rows - 1 || rand() < 0.2) {
walls.push({ c, r });
}
}
}
// Second pass: check for enclosed spaces
for (let c = 1; c < cols - 1; c++) {
for (let r = 1; r < rows - 1; r++) {
// Skip if already a wall
if (walls.some(w => w.c === c && w.r === r)) continue;
// Check all four sides
const hasWallAbove = walls.some(w => w.c === c && w.r === r - 1);
const hasWallBelow = walls.some(w => w.c === c && w.r === r + 1);
const hasWallLeft = walls.some(w => w.c === c - 1 && w.r === r);
const hasWallRight = walls.some(w => w.c === c + 1 && w.r === r);
// If all sides are walls, make this spot a wall too
if (hasWallAbove && hasWallBelow && hasWallLeft && hasWallRight) {
walls.push({ c, r });
}
}
}
}
function generateDots() {
dots = [];
for (let c = 1; c < cols - 1; c++) {
for (let r = 1; r < rows - 1; r++) {
if (walls.some(w => w.c === c && w.r === r)) continue;
const isEnclosed =
walls.some(w => w.c === c + 1 && w.r === r) &&
walls.some(w => w.c === c - 1 && w.r === r) &&
walls.some(w => w.c === c && w.r === r + 1) &&
walls.some(w => w.c === c && w.r === r - 1);
if (!isEnclosed) dots.push({ c, r });
}
}
}
// ===== Game Loop & Rendering =====
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawWalls();
moveChar(pacman);
moveEnemy();
drawChar(pacman, "yellow");
drawChar(enemy, "red");
eatDots();
drawScore();
checkGameOver();
}
function drawWalls() {
ctx.fillStyle = "blue";
walls.forEach(w => {
ctx.fillRect(w.c * CELL_SIZE, w.r * CELL_SIZE, CELL_SIZE, CELL_SIZE);
});
}
function drawChar(ch, color) {
ctx.beginPath();
ctx.arc(ch.x, ch.y, ch.size, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
}
function drawScore() {
ctx.fillStyle = "white";
ctx.font = "20px Poppins";
ctx.textAlign = "left";
// Add padding to prevent clipping
const padding = 10;
ctx.fillText("Score: " + score, padding, 25);
}
function checkGameOver() {
if (
Math.abs(pacman.x - enemy.x) < pacman.size &&
Math.abs(pacman.y - enemy.y) < pacman.size
) {
ctx.fillStyle = "#00ff99";
ctx.font = "40px Poppins";
ctx.textAlign = "center";
ctx.fillText("Game Over!", canvas.width / 2, canvas.height / 2);
clearInterval(gameInterval);
}
}
// ===== Movement Logic =====
function movePacman(e) {
const k = e.key;
if (!["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(k)) return;
e.preventDefault();
if (k === "ArrowUp") { pacman.dx = 0; pacman.dy = -PACMAN_SPEED; }
if (k === "ArrowDown") { pacman.dx = 0; pacman.dy = PACMAN_SPEED; }
if (k === "ArrowLeft") { pacman.dx = -PACMAN_SPEED; pacman.dy = 0; }
if (k === "ArrowRight") { pacman.dx = PACMAN_SPEED; pacman.dy = 0; }
}
function moveChar(ch) {
const nx = ch.x + ch.dx;
const ny = ch.y + ch.dy;
if (!willCollide(nx, ny, ch.size)) {
ch.x = nx;
ch.y = ny;
}
}
function moveEnemy() {
const options = [];
const moves = [[ENEMY_SPEED, 0], [-ENEMY_SPEED, 0], [0, ENEMY_SPEED], [0, -ENEMY_SPEED]];
moves.forEach(([dx, dy]) => {
const nx = enemy.x + dx;
const ny = enemy.y + dy;
if (!willCollide(nx, ny, enemy.size)) options.push({ dx, dy });
});
if (!options.length) return;
let best = options[0];
let bestDist = dist(enemy.x + best.dx, enemy.y + best.dy, pacman.x, pacman.y);
for (const opt of options) {
const d = dist(enemy.x + opt.dx, enemy.y + opt.dy, pacman.x, pacman.y);
if (d < bestDist) {
best = opt;
bestDist = d;
}
}
enemy.x += best.dx;
enemy.y += best.dy;
}
function dist(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function willCollide(x, y, size) {
const left = x - size, right = x + size;
const top = y - size, bottom = y + size;
return walls.some(w => {
const wx1 = w.c * CELL_SIZE, wy1 = w.r * CELL_SIZE;
const wx2 = wx1 + CELL_SIZE, wy2 = wy1 + CELL_SIZE;
return right > wx1 && left < wx2 && bottom > wy1 && top < wy2;
});
}
function eatDots() {
const chompSound = document.getElementById("chomp-sound");
dots = dots.filter(d => {
const dx = d.c * CELL_SIZE + CELL_SIZE / 2;
const dy = d.r * CELL_SIZE + CELL_SIZE / 2;
if (Math.abs(pacman.x - dx) < pacman.size && Math.abs(pacman.y - dy) < pacman.size) {
score++;
if (chompSound) {
chompSound.currentTime = 0;
chompSound.volume = 0.4;
chompSound.play();
}
return false;
}
return true;
});
// Check if all dots are eaten
if (dots.length === 0) {
// Trigger password generator for new random map
const generateBtn = document.getElementById("generate-btn");
if (generateBtn) {
generateBtn.click();
}
// Auto-restart the game after a short delay
setTimeout(() => {
resetGame();
}, 1000);
}
ctx.fillStyle = "white";
dots.forEach(d => {
ctx.beginPath();
ctx.arc(d.c * CELL_SIZE + CELL_SIZE / 2, d.r * CELL_SIZE + CELL_SIZE / 2, DOT_SIZE, 0, Math.PI * 2);
ctx.fill();
});
}
// ===== Global Functions =====
window.resetGame = resetGame;
window.exitGame = exitGame;
// Add scroll prevention function
function preventScroll(e) {
e.preventDefault();
}
-475
View File
@@ -1,475 +0,0 @@
// ===== AES Advanced Encryption =====
async function encryptAdvanced(message, password) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const key = await deriveKey(password, salt);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoder = new TextEncoder();
const encodedMessage = encoder.encode(message);
const encryptedMessage = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv: iv },
key,
encodedMessage
);
const encryptedArray = new Uint8Array(salt.length + iv.length + encryptedMessage.byteLength);
encryptedArray.set(salt);
encryptedArray.set(iv, salt.length);
encryptedArray.set(new Uint8Array(encryptedMessage), salt.length + iv.length);
return btoa(String.fromCharCode(...encryptedArray));
}
async function deriveKey(password, salt) {
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(password);
const keyMaterial = await crypto.subtle.importKey(
'raw',
passwordBuffer,
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 200000,
hash: 'SHA-256',
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
async function decryptAdvanced(encryptedData, password) {
const encryptedArray = new Uint8Array(atob(encryptedData).split("").map(c => c.charCodeAt(0)));
const salt = encryptedArray.slice(0, 16);
const iv = encryptedArray.slice(16, 28);
const encryptedMessage = encryptedArray.slice(28);
const key = await deriveKey(password, salt);
const decryptedMessage = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
key,
encryptedMessage
);
const decoder = new TextDecoder();
return decoder.decode(decryptedMessage);
}
// ===== UI Toggles =====
function toggleEncryptionOptions() {
const type = document.getElementById("encryption-type").value;
document.getElementById("password-input").style.display = (type === 'advanced') ? 'flex' : 'none';
if (type === 'basic') removeFile();
toggleInputMode();
document.getElementById("encrypt-label").textContent = (type === 'basic') ? "Encode" : "Encrypt";
document.getElementById("decrypt-label").textContent = (type === 'basic') ? "Decode" : "Decrypt";
}
function removeFile() {
document.getElementById("file-input").value = "";
document.getElementById("remove-file-btn").style.display = 'none';
toggleInputMode();
}
function toggleInputMode() {
const textValue = document.getElementById("input-text").value.trim();
const fileSelected = document.getElementById("file-input").files.length > 0;
const isAdvanced = document.getElementById("encryption-type").value === 'advanced';
document.getElementById("text-section").style.display = fileSelected ? 'none' : 'flex';
document.getElementById("file-section").style.display = (isAdvanced && !textValue) ? 'flex' : 'none';
document.getElementById("remove-file-btn").style.display = fileSelected ? 'inline-block' : 'none';
if (isAdvanced) {
document.getElementById("password-input").style.display = 'flex';
}
}
// ===== Form Submission =====
async function handleSubmit(event) {
event.preventDefault();
const password = document.getElementById("password").value;
const encryptionType = document.getElementById("encryption-type").value;
if (encryptionType === 'advanced' && !password) {
alert("Password is required for advanced encryption.");
return;
}
const payload = {
"encryption-type": encryptionType,
operation: document.querySelector('input[name="operation"]:checked').value,
message: document.getElementById("input-text").value,
password: password
};
const fileInput = document.getElementById("file-input");
if (fileInput.files.length > 0) {
const op = document.querySelector('input[name="operation"]:checked').value;
if (op === 'encrypt') encryptFile();
else decryptFile();
return;
}
try {
const resp = await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
const data = await resp.json();
document.getElementById("output-text").value = data.result;
} catch (err) {
alert("Error processing request: " + err);
}
}
// ===== File Encryption / Decryption =====
function encryptFile() {
const f = document.getElementById("file-input");
const pwd = document.getElementById("password").value;
if (!pwd) return alert("Please enter a password!");
if (!f.files.length) return alert("Please select a file!");
const reader = new FileReader();
reader.onload = async (e) => {
const raw = new Uint8Array(e.target.result);
const base64Raw = btoa(String.fromCharCode(...raw));
const encrypted = await encryptAdvanced(base64Raw, pwd);
downloadFile(encrypted, f.files[0].name + ".enc");
};
reader.readAsArrayBuffer(f.files[0]);
}
function decryptFile() {
const f = document.getElementById("file-input");
const pwd = document.getElementById("password").value;
if (!pwd) return alert("Please enter a password!");
if (!f.files.length) return alert("Please select a file!");
const reader = new FileReader();
reader.onload = async (e) => {
try {
const encryptedText = e.target.result;
const decryptedBase64 = await decryptAdvanced(encryptedText, pwd);
const byteString = atob(decryptedBase64);
const byteArray = new Uint8Array([...byteString].map(c => c.charCodeAt(0)));
downloadFileBinary(byteArray, f.files[0].name.replace(/\.enc$/, ''));
} catch (err) {
alert("Decryption failed: wrong password or corrupted file.");
}
};
reader.readAsText(f.files[0]);
}
function downloadFile(content, filename) {
const blob = new Blob([content], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
function downloadFileBinary(byteArray, filename) {
const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
// ===== Password Generator =====
function generateRandomPassword() {
const length = 30;
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+[]{}|;:,.<>?/~";
let password = "";
for (let i = 0; i < length; i++) {
password += charset.charAt(Math.floor(Math.random() * charset.length));
}
document.getElementById("password-field").value = password;
}
// ===== Copy to Clipboard =====
function copyToClipboard(elementId, toastId) {
const copyText = document.getElementById(elementId);
copyText.select();
copyText.setSelectionRange(0, 99999);
document.execCommand("copy");
const toast = document.getElementById(toastId);
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 2000);
}
// ===== Pacman Easter Egg =====
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';
startPacman();
} else if (pacSection.style.display === 'block' && !val.includes('pacman')) {
exitGame();
}
}
// ===== Game Exit & Restart =====
function exitGame() {
stopPacman();
document.getElementById("input-text").value = "";
document.getElementById("pacman-section").style.display = 'none';
document.getElementById("encoding-section").style.display = 'block';
}
function resetGame() {
stopPacman();
startPacman();
}
// ===== Clear All =====
function clearAll() {
document.getElementById("input-text").value = "";
document.getElementById("output-text").value = "";
document.getElementById("file-input").value = "";
document.getElementById("password").value = "";
document.getElementById("pacman-section").style.display = "none";
document.getElementById("encoding-section").style.display = "block";
removeFile();
toggleInputMode();
}
// ===== Initialize =====
document.addEventListener("DOMContentLoaded", () => {
toggleEncryptionOptions();
toggleInputMode();
document.getElementById("input-text").addEventListener("input", checkForPacman);
});
// ===== Pacman Game Variables & Logic =====
let canvas, ctx, pacman, enemy, walls, dots, score;
let pacmanSpeed = 40, enemySpeed = 20, cellSize = 40, dotSize = 5;
let cols, rows, randSeed, gameInterval;
function startPacman() {
canvas = document.getElementById("pacmanCanvas");
ctx = canvas.getContext("2d");
cols = Math.floor(canvas.width / cellSize);
rows = Math.floor(canvas.height / cellSize);
walls = []; dots = []; score = 0;
clearInterval(gameInterval);
randSeed = Array.from(
document.getElementById("password-field").value
).reduce((s, c) => s + c.charCodeAt(0), 0);
generateWalls();
generateDots();
pacman = spawn();
do {
enemy = spawn();
} while (enemy.x === pacman.x && enemy.y === pacman.y);
pacman.dx = pacman.dy = 0;
document.addEventListener("keydown", movePacman);
gameInterval = setInterval(gameLoop, 150);
}
function stopPacman() {
clearInterval(gameInterval);
if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function spawn() {
const opts = [];
for (let c = 1; c < cols - 1; c++) {
for (let r = 1; r < rows - 1; r++) {
if (!walls.some(w => w.c === c && w.r === r)) {
const neighbors = [
{ c: c+1, r }, { c: c-1, r },
{ c, r: r+1 }, { c, r: r-1 }
];
if (neighbors.some(n =>
!walls.some(w => w.c===n.c && w.r===n.r)
)) {
opts.push({ c, r });
}
}
}
}
const s = opts[Math.floor(rand() * opts.length)];
return {
x: s.c * cellSize + cellSize/2,
y: s.r * cellSize + cellSize/2,
size: cellSize/2 - 5,
dx: 0,
dy: 0
};
}
function rand() {
const x = Math.sin(randSeed++) * 10000;
return x - Math.floor(x);
}
function generateWalls() {
for (let c = 0; c < cols; c++) {
for (let r = 0; r < rows; r++) {
if (c===0||r===0||c===cols-1||r===rows-1||rand()<0.2) {
walls.push({ c, r });
}
}
}
}
function generateDots() {
dots = [];
for (let c = 1; c < cols - 1; c++) {
for (let r = 1; r < rows - 1; r++) {
if (walls.some(w => w.c===c && w.r===r)) continue;
const isEnclosed =
walls.some(w => w.c===c+1 && w.r===r) &&
walls.some(w => w.c===c-1 && w.r===r) &&
walls.some(w => w.c===c && w.r===r+1) &&
walls.some(w => w.c===c && w.r===r-1);
if (!isEnclosed) dots.push({ c, r });
}
}
}
function movePacman(e) {
if (!["ArrowUp","ArrowDown","ArrowLeft","ArrowRight"].includes(e.key)) return;
e.preventDefault();
if (e.key==="ArrowUp") { pacman.dx=0; pacman.dy=-pacmanSpeed; }
if (e.key==="ArrowDown") { pacman.dx=0; pacman.dy=pacmanSpeed; }
if (e.key==="ArrowLeft") { pacman.dx=-pacmanSpeed; pacman.dy=0; }
if (e.key==="ArrowRight") { pacman.dx=pacmanSpeed; pacman.dy=0; }
}
// ===== Collision Helper =====
function willCollide(x, y, size) {
const left = x - size, right = x + size;
const top = y - size, bottom = y + size;
for (let w of walls) {
const wx1 = w.c * cellSize, wy1 = w.r * cellSize;
const wx2 = wx1 + cellSize, wy2 = wy1 + cellSize;
if (right > wx1 && left < wx2 && bottom > wy1 && top < wy2) {
return true;
}
}
return false;
}
function moveChar(ch) {
const nx = ch.x + ch.dx, ny = ch.y + ch.dy;
if (!willCollide(nx, ny, ch.size)) {
ch.x = nx; ch.y = ny;
}
}
function moveEnemy() {
const options = [];
[[enemySpeed,0],[-enemySpeed,0],[0,enemySpeed],[0,-enemySpeed]].forEach(
([dx,dy]) => {
const nx = enemy.x + dx, ny = enemy.y + dy;
if (!willCollide(nx, ny, enemy.size)) options.push({dx,dy});
}
);
if (!options.length) return;
let best = options[0];
let bestD = Math.abs(enemy.x+best.dx-pacman.x)+Math.abs(enemy.y+best.dy-pacman.y);
for (let opt of options) {
const d = Math.abs(enemy.x+opt.dx-pacman.x)+Math.abs(enemy.y+opt.dy-pacman.y);
if (d < bestD) { best=opt; bestD=d; }
}
enemy.x += best.dx; enemy.y += best.dy;
}
function gameLoop() {
ctx.clearRect(0,0,canvas.width,canvas.height);
drawWalls();
moveChar(pacman);
moveEnemy();
drawChar(pacman,"yellow");
drawChar(enemy,"red");
eatDots();
drawScore();
checkGameOver();
}
function drawWalls() {
ctx.fillStyle="blue";
walls.forEach(w=>ctx.fillRect(w.c*cellSize,w.r*cellSize,cellSize,cellSize));
}
function drawChar(ch,color) {
ctx.beginPath();
ctx.arc(ch.x,ch.y,ch.size,0,Math.PI*2);
ctx.fillStyle=color; ctx.fill();
}
function eatDots() {
const chompSound = document.getElementById("chomp-sound");
dots = dots.filter(d => {
const dx = d.c * cellSize + cellSize / 2;
const dy = d.r * cellSize + cellSize / 2;
if (Math.abs(pacman.x - dx) < pacman.size && Math.abs(pacman.y - dy) < pacman.size) {
score++;
if (chompSound) {
chompSound.currentTime = 0; // Reset sound
chompSound.volume = 0.4;
chompSound.play();
}
return false; // Remove dot
}
return true;
});
ctx.fillStyle = "white";
dots.forEach(d => {
ctx.beginPath();
ctx.arc(d.c * cellSize + cellSize / 2, d.r * cellSize + cellSize / 2, dotSize, 0, Math.PI * 2);
ctx.fill();
});
}
function drawScore() {
ctx.fillStyle="white";
ctx.font="20px Poppins";
ctx.fillText("Score: "+score,10,25);
}
function checkGameOver() {
if (Math.abs(pacman.x-enemy.x)<pacman.size && Math.abs(pacman.y-enemy.y)<pacman.size) {
ctx.fillStyle="#00ff99";
ctx.font="40px Poppins";
ctx.textAlign="center";
ctx.fillText("Game Over!", canvas.width/2, canvas.height/2);
clearInterval(gameInterval);
}
}
+301
View File
@@ -0,0 +1,301 @@
/**
* 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", updateToggleLabels);
// 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() { }
+40 -31
View File
@@ -1,42 +1,51 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>403 - PacCrypt</title> <meta name="description" content="PacCrypt - 403 Forbidden Access" />
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}"> <title>403 Forbidden - PacCrypt</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head> </head>
<body class="dark"> <body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt</h1>
<p>Encrypt and share your text or files securely</p>
</header>
<header class="card" style="margin-bottom: 20px;"> <!-- Main Content -->
<h1>PacCrypt</h1> <main>
<p>Secure Encoding, Encryption and Password Generation</p> <section class="card form-group" style="padding: 50px 30px;">
</header> <h2 style="color: #00ff99; font-size: 2.5em;">🚫 403 - Forbidden</h2>
<p class="mt-4" style="font-size: 1.2em; color: #cccccc;">
Looks like this area is locked behind a secret ghost door! 🛡️👻
</p>
<main> <!-- Navigation -->
<section class="card" style="padding: 50px 30px;"> <div class="button-group mt-4">
<h2 style="color: #00ff99; font-size: 2.5em;">403 - Forbidden</h2> <a href="{{ url_for('index') }}">
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;"> <button type="button">⬅️ Return Home</button>
Looks like this area is locked behind a secret ghost door! 🛡️👻 </a>
</p> </div>
</section>
</main>
<div class="button-group" style="margin-top: 30px;"> <!-- Footer -->
<a href="{{ url_for('index') }}"> <footer>
<button type="button">Return Home</button> <p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a> </a>
</div> </footer>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body> </body>
</html> </html>
+40 -31
View File
@@ -1,42 +1,51 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 - PacCrypt</title> <meta name="description" content="PacCrypt - 404 Page Not Found" />
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}"> <title>404 Not Found - PacCrypt</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head> </head>
<body class="dark"> <body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt</h1>
<p>Encrypt and share your text or files securely</p>
</header>
<header> <!-- Main Content -->
<h1>PacCrypt</h1> <main>
<p>Secure Encoding, Encryption and Password Generation</p> <section class="card form-group" style="padding: 50px 30px;">
</header> <h2 style="color: #ff0066; font-size: 2.5em;">❓ 404 - Not Found</h2>
<p class="mt-4" style="font-size: 1.2em; color: #cccccc;">
Whoops! That page doesn't seem to exist. Maybe it got encrypted? 🧩🔐
</p>
<main> <!-- Navigation -->
<div class="button-group mt-4">
<a href="{{ url_for('index') }}">
<button type="button">⬅️ Return Home</button>
</a>
</div>
</section>
</main>
<section class="card"> <!-- Footer -->
<h2>404 - Page Not Found</h2> <footer>
<p>Oops! The page you're looking for doesn't exist.</p> <p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<div class="button-group" style="margin-top: 20px;"> <img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
<a href="{{ url_for('index') }}">
<button type="button">Return Home</button>
</a> </a>
</div> </footer>
</section>
</main>
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body> </body>
</html> </html>
+41 -31
View File
@@ -1,42 +1,52 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>500 - PacCrypt</title> <meta name="description" content="PacCrypt - 500 Internal Server Error" />
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}"> <title>500 Server Error - PacCrypt</title>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head> </head>
<body class="dark"> <body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt</h1>
<p>Encrypt and share your text or files securely</p>
</header>
<header class="card" style="margin-bottom: 20px;"> <!-- Main Content -->
<h1>PacCrypt</h1> <main>
<p>Secure Encoding, Encryption and Password Generation</p> <section class="card form-group" style="padding: 50px 30px;">
</header> <h2 style="color: #ff3300; font-size: 2.5em;">💥 500 - Server Error</h2>
<p class="mt-4" style="font-size: 1.2em; color: #cccccc;">
Uh oh! The ghosts chomped the server wires. 🧟‍♂️👾
We're working on patching it up.
</p>
<main> <!-- Navigation -->
<section class="card" style="padding: 50px 30px;"> <div class="button-group mt-4">
<h2 style="color: #00ff99; font-size: 2.5em;">500 - Server Error</h2> <a href="{{ url_for('index') }}">
<p style="margin-top: 20px; font-size: 1.2em; color: #cccccc;"> <button type="button">⬅️ Return Home</button>
Uh oh! The ghosts chomped the server. 🧟‍♂️ </a>
</p> </div>
</section>
</main>
<div class="button-group" style="margin-top: 30px;"> <!-- Footer -->
<a href="{{ url_for('index') }}"> <footer>
<button type="button">Return Home</button> <p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a> </a>
</div> </footer>
</section>
</main>
<footer class="card" style="margin-top: 20px;">
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265"
alt="GitHub Logo" width="100">
</a>
</footer>
</body> </body>
</html> </html>
+257
View File
@@ -0,0 +1,257 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="PacCrypt - Admin Panel" />
<title>Admin Panel - PacCrypt</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head>
<body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt Admin Panel</h1>
<p>Site Overview & Controls</p>
</header>
<!-- Main Content -->
<main>
<!-- Site Map Section -->
<section id="sitemap-section" class="card form-group">
<h2>💾 Server Management</h2>
<div class="sitemap-header">
<button onclick="toggleSitemap()" style="margin-bottom: 10px;">Show Site Map</button>
</div>
<div id="sitemap-list" class="sitemap-content" style="display: none;">
<ul style="list-style: none; padding-left: 0;">
{% for route in routes %}
<li style="margin-bottom: 5px;">🔗 <code>{{ route }}</code></li>
{% endfor %}
</ul>
</div>
<!-- Action Buttons -->
<div class="button-group">
<button onclick="restartServer()">🔁 Restart Server</button>
<a href="{{ url_for('admin_logout') }}">
<button type="button">🚪 Log Out</button>
</a>
</div>
<!-- Update and Settings Buttons -->
<div class="button-group">
<button onclick="updateServer()">🔁 Pull Latest Changes</button>
<a href="{{ url_for('admin_settings') }}">
<button type="button">🛠️ Manage Upload Settings</button>
</a>
</div>
<!-- Admin Reset and Clear Uploads Buttons -->
<div class="button-group">
<button onclick="resetAdmin()" class="danger-button">⚠️ Reset Admin</button>
<button onclick="clearUploads()" class="danger-button">🗑 Clear Uploaded Files</button>
</div>
<!-- Flash Messages -->
<div id="admin-feedback" class="copy-feedback" style="display: none;"></div>
</section>
<!-- Password Change Section -->
<section id="password-change-section" class="card form-group">
<h2>🔑 Change Admin Password</h2>
<!-- Password Feedback -->
{% with messages = get_flashed_messages(with_categories=true, category_filter=['password-feedback']) %}
{% for category, message in messages %}
<div class="copy-feedback show">{{ message }}</div>
{% endfor %}
{% endwith %}
<!-- Password Change Form -->
<form method="POST" action="{{ url_for('admin_change_password') }}">
<input type="password" name="current_password" placeholder="Current Password" required />
<input type="password" name="new_password" placeholder="New Password" required />
<button type="submit">Update Password</button>
</form>
</section>
<!-- Server Status Section -->
<section id="server-status-section" class="card form-group">
<h2>📊 Server Status</h2>
<ul style="list-style: none; padding-left: 0;">
<li>🕒 Uptime: <code>{{ server_info.uptime }}</code></li>
<li>📅 Server Time: <code>{{ server_info.time }}</code></li>
<li>🐍 Python Version: <code>{{ server_info.python }}</code></li>
<li>⚙️ Flask Debug Mode: <code>{{ server_info.debug }}</code></li>
</ul>
</section>
<!-- Server Logs Section -->
<section id="server-logs-section" class="card form-group">
<h2>📜 Server Logs</h2>
<button onclick="toggleLogs()" style="margin-bottom: 10px;">🔽 Show/Hide Logs</button>
<div id="logLoader" style="display: none; margin-bottom: 10px;">Loading logs...</div>
<pre id="logContainer" style="display: none;"></pre>
</section>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
<a href="{{ url_for('sitemap') }}">
<img src="\static\img\sitemap.png" alt="Sitemap Png" width="55" />
</a>
</footer>
<!-- Log Viewer Script -->
<script>
async function toggleLogs() {
const logContainer = document.getElementById('logContainer');
const logLoader = document.getElementById('logLoader');
if (logContainer.style.display === 'none') {
logLoader.style.display = 'block';
const response = await fetch('{{ url_for('admin_logs') }}');
const data = await response.json();
logLoader.style.display = 'none';
logContainer.innerText = data.logs.join('\\n');
logContainer.style.display = 'block';
} else {
logContainer.style.display = 'none';
}
}
</script>
<script>
function toggleSitemap() {
const list = document.getElementById('sitemap-list');
const button = document.querySelector('.sitemap-header button');
if (list.style.display === 'none') {
list.style.display = 'block';
button.textContent = 'Hide Site Map';
} else {
list.style.display = 'none';
button.textContent = 'Show Site Map';
}
}
async function restartServer() {
if (!confirm('Are you sure you want to restart the server? This will temporarily disconnect all users.')) return;
try {
const response = await fetch('{{ url_for("restart_server") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok) {
showFeedback(data.message);
// Add a small delay before redirecting to allow the server to restart
setTimeout(() => {
window.location.reload();
}, 2000);
} else {
showFeedback(data.error || 'Failed to restart server.');
}
} catch (error) {
showFeedback('Failed to restart server.');
}
}
async function updateServer() {
if (!confirm('Are you sure you want to pull the latest changes from GitHub?')) return;
try {
const response = await fetch('{{ url_for("admin_update_server") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok) {
showFeedback(data.message);
} else {
showFeedback(data.error || 'Failed to update server from GitHub.');
}
} catch (error) {
showFeedback('Failed to update server from GitHub.');
}
}
async function resetAdmin() {
if (!confirm('Are you sure you want to reset admin credentials?')) return;
try {
const response = await fetch('{{ url_for("admin_reset") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
showFeedback('Admin credentials reset. Please create new credentials.');
setTimeout(() => {
window.location.href = '{{ url_for("admin_setup") }}';
}, 2000);
}
} catch (error) {
showFeedback('Failed to reset admin credentials.');
}
}
async function clearUploads() {
if (!confirm('Are you sure you want to delete ALL uploaded files?')) return;
try {
const response = await fetch('{{ url_for("admin_clear_uploads") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
showFeedback('All uploaded files have been cleared.');
}
} catch (error) {
showFeedback('Failed to clear uploaded files.');
}
}
function showFeedback(message) {
const feedback = document.getElementById('admin-feedback');
feedback.textContent = message;
feedback.style.display = 'block';
feedback.classList.add('show');
setTimeout(() => {
feedback.classList.remove('show');
setTimeout(() => {
feedback.style.display = 'none';
}, 300);
}, 3000);
}
</script>
</body>
</html>
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="PacCrypt - Admin Login" />
<title>Admin Login - PacCrypt</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head>
<body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt Admin</h1>
<p>Administrator Login</p>
</header>
<!-- Main Content -->
<main>
<!-- Login Form Section -->
<section class="card form-group">
<h2>🔑 Admin Login</h2>
<!-- Flash Messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
<p style="color: red;">{{ messages[0] }}</p>
{% endif %}
{% endwith %}
<!-- Login Form -->
<form method="POST">
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<div class="button-group mt-3">
<button type="submit">🚪 Log In</button>
</div>
</form>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>
+70
View File
@@ -0,0 +1,70 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="PacCrypt - Admin Settings" />
<title>Admin Settings - PacCrypt</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
</head>
<body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt Admin Settings</h1>
<p>Manage upload configuration</p>
</header>
<!-- Main Content -->
<main>
<!-- Settings Form Section -->
<section class="card form-group">
<h2>⚙️ Upload Settings</h2>
<!-- Flash Messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul style="color: lime;">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<!-- Settings Form -->
<form method="POST">
<label for="upload_folder">Upload Folder Path:</label>
<input type="text" name="upload_folder" id="upload_folder" value="{{ settings.upload_folder }}" required />
<label for="max_file_age_days">Max File Age (Days):</label>
<input type="number" name="max_file_age_days" id="max_file_age_days" value="{{ settings.max_file_age_days }}" min="1" required />
<label for="max_file_size_gb">Max File Size (GB):</label>
<input type="number" name="max_file_size_gb" id="max_file_size_gb" value="{{ settings.max_file_size_bytes // (1024 * 1024 * 1024) }}" step="0.1" min="0.1" required />
<!-- Action Buttons -->
<div class="button-group mt-4">
<button type="submit">💾 Save Settings</button>
<a href="{{ url_for('admin_page') }}">
<button type="button">⬅️ Back to Admin Panel</button>
</a>
</div>
</form>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>
+58
View File
@@ -0,0 +1,58 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="PacCrypt - Admin Setup" />
<title>Admin Setup - PacCrypt</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" />
<!-- Stylesheets -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<!-- Scripts -->
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
</head>
<body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt Admin</h1>
<p>Admin Setup</p>
</header>
<!-- Main Content -->
<main>
<!-- Setup Form Section -->
<section class="card form-group">
<h2>🛡️ Create Admin Account</h2>
<!-- Flash Messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
<p style="color: red;">{{ messages[0] }}</p>
{% endif %}
{% endwith %}
<!-- Setup Form -->
<form method="POST">
<input type="text" name="username" placeholder="Username" required />
<input type="password" name="password" placeholder="Password" required />
<div class="button-group mt-3">
<button type="submit">📝 Set Credentials</button>
</div>
</form>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>
+206 -124
View File
@@ -1,124 +1,206 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PacCrypt</title> <meta name="description" content="PacCrypt - Secure text and file encryption with password generation" />
<title>PacCrypt - Encrypt and share your text or files securely</title>
<!-- Favicon -->
<link rel="icon" type="image/png" href="{{ url_for('static', filename='img/PacCrypt.png') }}" /> <!-- Favicon -->
<link rel="icon" href="{{ url_for('static', filename='img/PacCrypt.png') }}" type="image/png" />
<!-- Styles and Scripts -->
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet" /> <!-- Stylesheets -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<script defer src="{{ url_for('static', filename='js/script.js') }}"></script> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
</head>
<!-- Scripts -->
<body class="dark"> <script type="module" src="{{ url_for('static', filename='js/main.js') }}" defer></script>
</head>
<!-- Header --> <body class="dark">
<header> <!-- Header -->
<h1>PacCrypt</h1> <header class="card">
<p>Secure Encoding, Encryption and Password Generation</p> <h1>PacCrypt</h1>
</header> <p>Encrypt and share your text or files securely</p>
</header>
<main>
<!-- Main Content -->
<!-- Password Generator Section --> <main>
<section id="password-section" class="card"> <!-- Password Generator Section -->
<h2>Password Generator</h2> <section id="password-generator-section" class="card form-group">
<div class="form-group"> <h2>🔑 Password Generator</h2>
<input type="text" id="password-field" placeholder="Generated password will appear here" readonly /> <div class="form-group">
<div class="button-group"> <input type="text" id="generated-password" readonly />
<button type="button" onclick="generateRandomPassword()">Generate</button> <div class="button-group">
<button type="button" onclick="copyToClipboard('password-field', 'password-toast')">Copy</button> <button type="button" id="generate-btn">🎲 Generate</button>
</div> <button type="button" id="copy-btn">📋 Copy Password</button>
<div id="password-toast" class="toast">Copied to Clipboard!</div> </div>
</div> <div id="password-copy-feedback" class="copy-feedback">Password copied to clipboard!</div>
</section> </div>
</section>
<!-- Pacman Game Section -->
<section id="pacman-section" class="card" style="display: none;"> <!-- Pacman Game Section -->
<div class="pacman-wrapper"> <section id="pacman-section" class="card" style="display: none;">
<canvas id="pacmanCanvas" width="800" height="600"></canvas> <div class="pacman-wrapper">
</div> <canvas id="pacmanCanvas" width="800" height="600"></canvas>
<audio id="chomp-sound" src="{{ url_for('static', filename='audio/chomp.mp3') }}"></audio> </div>
<div class="button-group"> <audio id="chomp-sound" src="{{ url_for('static', filename='audio/chomp.mp3') }}"></audio>
<button type="button" onclick="resetGame()">Restart Game</button> <div class="button-group">
<button type="button" onclick="exitGame()">Exit Game</button> <button type="button" onclick="resetGame()">Restart Game</button>
</div> <button type="button" onclick="exitGame()">Exit Game</button>
</section> </div>
</section>
<!-- Text Encoder/Decoder & File Encrypt/Decrypt Section -->
<section id="encoding-section" class="card"> <!-- Encryption/Decryption Section -->
<h2>Text Encoder / Decoder & File Encryption</h2> <section id="encoding-section" class="card form-group">
<form id="main-form" class="form-group" method="POST" onsubmit="handleSubmit(event)"> <h2>🔐 Encrypt & Decrypt</h2>
<form id="crypto-form" class="form-group">
<!-- Encryption Type Dropdown --> <!-- Encryption Type Selection -->
<label for="encryption-type">Select Encryption Type:</label> <div class="form-group">
<select id="encryption-type" name="encryption-type" onchange="toggleEncryptionOptions()"> <label for="encryption-type">Encryption Type:</label>
<option value="basic">Basic (Less Secure)</option> <select id="encryption-type">
<option value="advanced" selected>Advanced (More Secure)</option> <option value="basic">Basic Cipher</option>
</select> <option value="advanced" selected>Advanced AES</option>
</select>
<!-- Encrypt / Decrypt Radio Buttons --> </div>
<div id="encryption-options" class="radio-group">
<label class="radio-button"> <!-- Operation Toggle -->
<input type="radio" name="operation" value="encrypt" id="encrypt-radio" checked /> <div class="toggle-container">
<span id="encrypt-label">Encrypt</span> <span id="toggle-left-label">Encrypt</span>
</label> <label class="switch">
<label class="radio-button"> <input type="checkbox" id="operation-toggle" />
<input type="radio" name="operation" value="decrypt" id="decrypt-radio" /> <span class="slider round"></span>
<span id="decrypt-label">Decrypt</span> </label>
</label> <span id="toggle-right-label">Decrypt</span>
</div> </div>
<!-- Text Area Input --> <!-- Text Input Section -->
<div id="text-section" class="form-group"> <div id="text-section" class="form-group">
<textarea id="input-text" name="message" placeholder="Enter text here..." oninput="toggleInputMode()"></textarea> <textarea id="input-text" placeholder="Enter your message..."></textarea>
</div> </div>
<!-- Password Input (shared for file + text) --> <!-- Password Input -->
<div id="password-input" class="form-group"> <div id="password-input" class="form-group">
<input type="password" id="password" name="password" placeholder="Enter Password" /> <input type="password" id="password" placeholder="Encryption/Decryption Password" />
</div> </div>
<!-- File Upload Section --> <!-- File Input Section -->
<div id="file-section" class="form-group" style="display: none;"> <div id="file-section" class="form-group" style="display: none;">
<input type="file" id="file-input" onchange="toggleInputMode()" /> <input type="file" id="file-input" />
<button type="button" id="remove-file-btn" onclick="removeFile()">Remove File</button> <button type="button" id="remove-file-btn">🗑 Remove File</button>
</div> </div>
<!-- Submit Button --> <!-- Action Buttons -->
<div class="button-group"> <div class="button-group">
<button type="submit" class="submit-button">Submit</button> <button type="submit">⚡ Execute</button>
</div> <button type="button" id="copy-output-btn">📋 Copy Output</button>
</form> </div>
<!-- Output Text Area --> <!-- Output Section -->
<div style="height: 20px;"></div> <textarea id="output-text" readonly placeholder="Encrypted/Decrypted text will appear here..."></textarea>
<textarea id="output-text" readonly placeholder="Result will appear here">{{ result }}</textarea> <div class="button-group">
<button type="button" id="clear-all-btn" class="danger-button">🧹 Clear All</button>
<!-- Output Controls --> </div>
<div class="button-group"> <div id="output-copy-feedback" class="copy-feedback">Text copied to clipboard!</div>
<button type="button" onclick="copyToClipboard('output-text', 'output-toast')">Copy Output</button> </form>
<button type="button" onclick="clearAll()">Clear All</button> </section>
</div>
<!-- File Sharing Section -->
<!-- Output Toast Notification --> <section id="sharing-section" class="card form-group">
<div id="output-toast" class="toast">Copied to Clipboard!</div> <h2>📤 PacCrypt Share</h2>
</section> <h3>Securely share encrypted files.</h3>
<p>Do not lose your passwords, data will be lost forever!</p>
</main>
<!-- Flash Messages -->
<!-- Footer --> {% with messages = get_flashed_messages() %}
<footer> {% if messages %}
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p> <ul style="color: lime; list-style: none; padding-left: 0;">
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link"> {% for message in messages %}
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Flogos-world.net%2Fwp-content%2Fuploads%2F2020%2F11%2FGitHub-Logo.png&f=1&nofb=1&ipt=b9d67651e313b2cdbeae8a7ec9320dadb278a21a2e7217810b839c233c04f265" <li>
alt="GitHub Logo" width="100" /> {{ message | safe }}
</a> {% if "pickup" in message %}
</footer> <div class="share-link-container">
<a id="share-link" href="{{ message.split(' at ')[1] }}" target="_blank">{{ message.split(" at ")[1] }}</a>
</body> <!--- <span id="share-link">{{ message.split(" at ")[1] }}</span> --->
</html> <button type="button" id="copy-share-btn">📋 Copy Link</button>
<div id="shared-link-feedback" class="copy-feedback">Link copied to clipboard!</div>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
<script>window.onload = () => window.scrollTo(0, document.body.scrollHeight);</script>
{% endif %}
{% endwith %}
<!-- File Upload Form -->
<!-- Share Link Container (initially hidden) -->
<div class="share-link-container" id="share-link-container" style="display: none;">
<a id="share-link" href="#" target="_blank"></a>
<button type="button" id="copy-share-btn">📋 Copy Link</button>
<div id="shared-link-feedback" class="copy-feedback">Link copied to clipboard!</div>
</div>
<form method="POST" enctype="multipart/form-data" class="form-group" id="upload-form">
<input type="file" name="file" id="upload-file" required />
<input type="password" name="enc_password" placeholder="Encryption/Decryption Password" required />
<input type="password" name="pickup_password" placeholder="Pickup Password" required />
<div class="button-group">
<button type="submit">🔒 Upload and Generate Link</button>
</div>
</form>
<script>
document.getElementById('upload-form').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(e.target);
try {
const response = await fetch('/', {
method: 'POST',
body: formData
});
const data = await response.json();
if (data.error) {
alert(data.error);
return;
}
if (data.success && data.pickup_url) {
const shareLink = document.getElementById('share-link');
const shareLinkContainer = document.getElementById('share-link-container');
shareLink.href = data.pickup_url;
shareLink.textContent = data.pickup_url;
shareLinkContainer.style.display = 'flex';
// Clear form fields
document.getElementById('upload-file').value = '';
document.getElementsByName('enc_password')[0].value = '';
document.getElementsByName('pickup_password')[0].value = '';
// Scroll to the share link
shareLinkContainer.scrollIntoView({ behavior: 'smooth' });
}
} catch (error) {
alert('Error uploading file: ' + error.message);
}
});
</script>
<!-- File Limits Information -->
<p class="text-muted mt-3" style="font-size: 0.85em;">
Files expire after {{ settings.max_file_age_days }} days.<br />
Max file size: {{ settings.max_file_size_bytes // (1024 * 1024 * 1024) }} GB.
</p>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>
+94
View File
@@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="PacCrypt - Secure file pickup and decryption" />
<title>PacCrypt - Secure File Pickup</title>
<!-- Favicon -->
<link rel="icon" href="{{ url_for('static', filename='img/PacCrypt.png') }}" type="image/png" />
<!-- Stylesheets -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}" />
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600&display=swap" rel="stylesheet">
</head>
<body class="dark">
<!-- Header -->
<header class="card">
<h1>PacCrypt</h1>
<p>Secure File Pickup and Decryption</p>
</header>
<!-- Main Content -->
<main>
<!-- File Pickup Section -->
<section id="pickup-section" class="card form-group">
<h2>🔐 File Pickup</h2>
<!-- Flash Messages -->
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul style="color: lime; list-style: none; padding-left: 0;">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<!-- File Info -->
<div class="form-group">
<p style="color: #00ff99; margin-bottom: 15px;">File ID: <code>{{ file_id }}</code></p>
</div>
<!-- Pickup Form -->
<form method="POST" class="form-group">
<div class="form-group">
<input type="password"
name="pickup_password"
placeholder="Pickup Password"
required
autocomplete="off" />
</div>
<div class="form-group">
<input type="password"
name="enc_password"
placeholder="Encryption Password"
required
autocomplete="off" />
</div>
<div class="button-group">
<button type="submit">📥 Decrypt and Download</button>
</div>
</form>
</section>
<!-- Security Notice Section -->
<section id="security-notice-section" class="card form-group">
<h2>🛡️ Security Notice</h2>
<p style="color: #00ff99; text-align: center;">
Make sure you're on the correct domain before entering any passwords.<br>
Your file will be permanently deleted after download.
</p>
</section>
<!-- Link ID Section -->
<section id="link-id-section" class="card form-group">
<p style="color: #00ff99; text-align: center; font-family: monospace; font-size: 1.1em;">
Link ID: <code>{{ file_id }}</code>
</p>
</section>
</main>
<!-- Footer -->
<footer>
<p>&copy; 2025 UnNaturalll-Dev. All rights reserved.</p>
<a href="https://github.com/TySP-Dev" target="_blank" id="github-link">
<img src="\static\img\Github_logo.png" alt="GitHub Logo" width="100" />
</a>
</footer>
</body>
</html>