god i am done with this program
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
/venv
|
||||
assets/*
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,7 @@ from flask import Flask, request, render_template, redirect, url_for, session, s
|
||||
from flask_talisman import Talisman
|
||||
from functools import wraps
|
||||
import os
|
||||
from security import validate_user
|
||||
from security import validate_user, is_ip_locked
|
||||
from data_handler import save_link, save_file, retrieve_uploads, handle_download, get_file_path
|
||||
from datetime import datetime
|
||||
from zipfile import ZipFile
|
||||
@@ -36,11 +36,21 @@ def login_required(f):
|
||||
|
||||
@app.before_request
|
||||
def ensure_login():
|
||||
if 'username' not in session and request.endpoint not in ('login', 'static'):
|
||||
ip_address = request.remote_addr
|
||||
|
||||
# Check if the IP is locked and redirect to lockout if it is
|
||||
if is_ip_locked(ip_address) and request.endpoint != 'lockout':
|
||||
return redirect(url_for('lockout'))
|
||||
|
||||
if 'username' not in session and request.endpoint not in ('login', 'static', 'lockout'):
|
||||
return redirect(url_for('login'))
|
||||
elif request.endpoint == 'login' and 'username' in session:
|
||||
session.clear()
|
||||
|
||||
@app.route('/lockout')
|
||||
def lockout():
|
||||
return render_template("lockout.html")
|
||||
|
||||
@app.route('/')
|
||||
@login_required
|
||||
def index():
|
||||
@@ -48,6 +58,12 @@ def index():
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
ip_address = request.remote_addr
|
||||
|
||||
# Check if the IP is locked out
|
||||
if is_ip_locked(ip_address):
|
||||
return redirect(url_for('lockout'))
|
||||
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
@@ -77,14 +93,18 @@ def upload_link():
|
||||
@app.route('/upload/files', methods=['POST'])
|
||||
@login_required
|
||||
def upload_files():
|
||||
if 'files' not in request.files:
|
||||
# Check if the field name 'files[]' is present in the request
|
||||
if 'files[]' not in request.files:
|
||||
return redirect(url_for('index'))
|
||||
|
||||
files = request.files.getlist('files')
|
||||
# Retrieve the list of files with 'files[]'
|
||||
files = request.files.getlist('files[]')
|
||||
uploader = session['username']
|
||||
|
||||
# Save each file
|
||||
for file in files:
|
||||
save_file(uploader, file)
|
||||
if file: # Check if a file was actually selected
|
||||
save_file(uploader, file)
|
||||
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
||||
@@ -99,8 +99,14 @@ def reset_login_attempts(username):
|
||||
|
||||
def increment_login_attempts(username):
|
||||
with closing(sqlite3.connect(DATABASE)) as conn, conn, closing(conn.cursor()) as c:
|
||||
c.execute('UPDATE users SET login_attempts = login_attempts + 1 WHERE username = ?', (username,))
|
||||
conn.commit()
|
||||
if username:
|
||||
c.execute('UPDATE users SET login_attempts = login_attempts + 1 WHERE username = ?', (username,))
|
||||
c.execute('SELECT login_attempts FROM users WHERE username = ?', (username,))
|
||||
login_attempts = c.fetchone()[0]
|
||||
conn.commit()
|
||||
return login_attempts
|
||||
else:
|
||||
return None
|
||||
|
||||
def add_upload(uploader, file_type, content):
|
||||
with closing(sqlite3.connect(DATABASE)) as conn, conn, closing(conn.cursor()) as c:
|
||||
|
||||
39
server/security.add.py
Normal file
39
server/security.add.py
Normal file
@@ -0,0 +1,39 @@
|
||||
def validate_user(username, password):
|
||||
ip_address = request.remote_addr
|
||||
|
||||
# Check if the IP is locked
|
||||
if is_ip_locked(ip_address):
|
||||
return False, "You have been locked out."
|
||||
|
||||
user_data = get_user(username)
|
||||
if not user_data:
|
||||
increment_login_attempts(None) # Increment failed attempts for any non-existent username attempt
|
||||
|
||||
# Check if IP should be locked
|
||||
attempts = increment_login_attempts(None)
|
||||
if attempts >= MAX_ATTEMPTS:
|
||||
lock_ip(ip_address)
|
||||
return False, "Maximum login attempts exceeded. You have been locked out."
|
||||
|
||||
remaining_attempts = MAX_ATTEMPTS - attempts
|
||||
return False, f"User does not exist. {remaining_attempts} attempt(s) remaining."
|
||||
|
||||
stored_password, salt, login_attempts = user_data
|
||||
|
||||
# Check if the maximum login attempts have been reached
|
||||
if login_attempts >= MAX_ATTEMPTS:
|
||||
lock_ip(ip_address)
|
||||
return False, "Maximum login attempts exceeded. You have been locked out."
|
||||
|
||||
hashed_password = hash_password(password, salt)
|
||||
if hashed_password == stored_password:
|
||||
reset_login_attempts(username)
|
||||
return True, "Login successful."
|
||||
else:
|
||||
increment_login_attempts(username)
|
||||
if login_attempts + 1 >= MAX_ATTEMPTS:
|
||||
lock_ip(ip_address)
|
||||
return False, "Maximum login attempts exceeded. You have been locked out."
|
||||
|
||||
remaining_attempts = MAX_ATTEMPTS - login_attempts - 1
|
||||
return False, f"Invalid credentials. {remaining_attempts} attempt(s) remaining."
|
||||
@@ -1,9 +1,16 @@
|
||||
import os # Import for generating random salts
|
||||
import os
|
||||
import hashlib
|
||||
from flask import request
|
||||
from db_setup import get_user, increment_login_attempts, reset_login_attempts
|
||||
|
||||
MAX_ATTEMPTS = 3
|
||||
LOCKOUT_FILE = "locked_ips.txt"
|
||||
FAILED_ATTEMPTS = {}
|
||||
|
||||
# Ensure the locked_ips.txt file exists
|
||||
if not os.path.exists(LOCKOUT_FILE):
|
||||
with open(LOCKOUT_FILE, 'w') as f:
|
||||
pass
|
||||
|
||||
def generate_salt():
|
||||
"""
|
||||
@@ -12,39 +19,68 @@ def generate_salt():
|
||||
return os.urandom(16)
|
||||
|
||||
def hash_password(password, salt):
|
||||
"""
|
||||
Hashes the password with the provided salt using SHA-256.
|
||||
"""
|
||||
# Convert the salt to bytes if it's a string
|
||||
if isinstance(salt, str):
|
||||
salt = salt.encode()
|
||||
return hashlib.sha256(salt + password.encode()).hexdigest()
|
||||
|
||||
def is_ip_locked(ip):
|
||||
"""
|
||||
Checks if the IP address is in the lockout list.
|
||||
"""
|
||||
if os.path.exists(LOCKOUT_FILE):
|
||||
with open(LOCKOUT_FILE, 'r') as f:
|
||||
locked_ips = f.read().splitlines()
|
||||
return ip in locked_ips
|
||||
return False
|
||||
|
||||
def lock_ip(ip):
|
||||
"""
|
||||
Adds an IP address to the lockout list.
|
||||
"""
|
||||
with open(LOCKOUT_FILE, 'a') as f:
|
||||
f.write(ip + "\n")
|
||||
|
||||
def validate_user(username, password):
|
||||
"""
|
||||
Validates the user's credentials against stored data.
|
||||
"""
|
||||
ip_address = request.remote_addr
|
||||
|
||||
# Check if the IP is locked
|
||||
if is_ip_locked(ip_address):
|
||||
return False, "You have been locked out."
|
||||
|
||||
# Check or increment failed attempts for this IP address
|
||||
if ip_address not in FAILED_ATTEMPTS:
|
||||
FAILED_ATTEMPTS[ip_address] = 0
|
||||
|
||||
user_data = get_user(username)
|
||||
if not user_data:
|
||||
print(f"User '{username}' does not exist.")
|
||||
return False, "User does not exist."
|
||||
FAILED_ATTEMPTS[ip_address] += 1
|
||||
|
||||
if FAILED_ATTEMPTS[ip_address] >= MAX_ATTEMPTS:
|
||||
lock_ip(ip_address)
|
||||
return False, "Maximum login attempts exceeded. You have been locked out."
|
||||
|
||||
remaining_attempts = MAX_ATTEMPTS - FAILED_ATTEMPTS[ip_address]
|
||||
return False, f"User does not exist. {remaining_attempts} attempt(s) remaining."
|
||||
|
||||
stored_password, salt, login_attempts = user_data
|
||||
|
||||
# Check if the maximum login attempts have been reached
|
||||
if login_attempts >= MAX_ATTEMPTS:
|
||||
print(f"User '{username}' has exceeded max login attempts.")
|
||||
return False, "Maximum login attempts exceeded. Please contact the administrator."
|
||||
|
||||
# Hash the provided password with the salt
|
||||
hashed_password = hash_password(password, salt)
|
||||
print(f"Provided hash: {hashed_password}, Stored hash: {stored_password}")
|
||||
|
||||
if hashed_password == stored_password:
|
||||
reset_login_attempts(username)
|
||||
print(f"User '{username}' logged in successfully.")
|
||||
# Clear failed attempts for this IP on a successful login
|
||||
FAILED_ATTEMPTS.pop(ip_address, None)
|
||||
return True, "Login successful."
|
||||
else:
|
||||
increment_login_attempts(username)
|
||||
remaining_attempts = MAX_ATTEMPTS - login_attempts - 1
|
||||
print(f"Invalid credentials for '{username}'. {remaining_attempts} attempt(s) remaining.")
|
||||
FAILED_ATTEMPTS[ip_address] += 1
|
||||
if FAILED_ATTEMPTS[ip_address] >= MAX_ATTEMPTS:
|
||||
lock_ip(ip_address)
|
||||
return False, "Maximum login attempts exceeded. You have been locked out."
|
||||
|
||||
remaining_attempts = MAX_ATTEMPTS - FAILED_ATTEMPTS[ip_address]
|
||||
return False, f"Invalid credentials. {remaining_attempts} attempt(s) remaining."
|
||||
|
||||
def identify_uploader():
|
||||
|
||||
79
server/security.py.bak
Normal file
79
server/security.py.bak
Normal file
@@ -0,0 +1,79 @@
|
||||
import os # Import for generating random salts
|
||||
import hashlib
|
||||
from flask import request
|
||||
from db_setup import get_user, increment_login_attempts, reset_login_attempts
|
||||
|
||||
MAX_ATTEMPTS = 3
|
||||
|
||||
def generate_salt():
|
||||
"""
|
||||
Generates a 16-byte random salt.
|
||||
"""
|
||||
return os.urandom(16)
|
||||
|
||||
def hash_password(password, salt):
|
||||
# Convert the salt to bytes if it's a string
|
||||
if isinstance(salt, str):
|
||||
salt = salt.encode()
|
||||
return hashlib.sha256(salt + password.encode()).hexdigest()
|
||||
|
||||
def validate_user(username, password):
|
||||
"""
|
||||
Validates the user's credentials against stored data.
|
||||
"""
|
||||
user_data = get_user(username)
|
||||
if not user_data:
|
||||
print(f"User '{username}' does not exist.")
|
||||
return False, "User does not exist."
|
||||
|
||||
stored_password, salt, login_attempts = user_data
|
||||
|
||||
# Check if the maximum login attempts have been reached
|
||||
if login_attempts >= MAX_ATTEMPTS:
|
||||
print(f"User '{username}' has exceeded max login attempts.")
|
||||
return False, "Maximum login attempts exceeded. Please contact the administrator."
|
||||
|
||||
# Hash the provided password with the salt
|
||||
hashed_password = hash_password(password, salt)
|
||||
print(f"Provided hash: {hashed_password}, Stored hash: {stored_password}")
|
||||
|
||||
if hashed_password == stored_password:
|
||||
reset_login_attempts(username)
|
||||
print(f"User '{username}' logged in successfully.")
|
||||
return True, "Login successful."
|
||||
else:
|
||||
increment_login_attempts(username)
|
||||
remaining_attempts = MAX_ATTEMPTS - login_attempts - 1
|
||||
print(f"Invalid credentials for '{username}'. {remaining_attempts} attempt(s) remaining.")
|
||||
return False, f"Invalid credentials. {remaining_attempts} attempt(s) remaining."
|
||||
|
||||
def identify_uploader():
|
||||
"""
|
||||
Identifies the uploader's device information from the request headers.
|
||||
"""
|
||||
device_info = get_device_info()
|
||||
user_agent = device_info['user_agent']
|
||||
|
||||
if "iPhone" in user_agent:
|
||||
device_type = "iPhone"
|
||||
elif "Android" in user_agent:
|
||||
device_type = "Android"
|
||||
elif "Windows" in user_agent:
|
||||
device_type = "Windows PC"
|
||||
elif "Mac" in user_agent:
|
||||
device_type = "Mac"
|
||||
elif "Linux" in user_agent:
|
||||
device_type = "Linux Machine"
|
||||
else:
|
||||
device_type = "Unknown Device"
|
||||
|
||||
return f"Uploaded by {device_type} (IP: {device_info['ip']})"
|
||||
|
||||
def get_device_info():
|
||||
"""
|
||||
Extracts device information from the request.
|
||||
"""
|
||||
return {
|
||||
"ip": request.remote_addr or "Unknown IP",
|
||||
"user_agent": request.headers.get('User-Agent', 'Unknown'),
|
||||
}
|
||||
47
static/error.css
Normal file
47
static/error.css
Normal file
@@ -0,0 +1,47 @@
|
||||
/* error.css */
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.error-container {
|
||||
background-color: #f5c6cb;
|
||||
padding: 20px 40px;
|
||||
border: 1px solid #f1b0b7;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #721c24;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
background-color: #f1b0b7;
|
||||
padding: 8px 15px;
|
||||
border-radius: 5px;
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
background-color: #f8d7da;
|
||||
}
|
||||
|
||||
BIN
static/eye.jpeg
Normal file
BIN
static/eye.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 315 KiB |
85
static/index.css
Normal file
85
static/index.css
Normal file
@@ -0,0 +1,85 @@
|
||||
/* static/index.css */
|
||||
body {
|
||||
background-color: #f0f4f8;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
max-width: 500px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #007acc;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin: 15px 0;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
form {
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
input[type="text"], input[type="file"] {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007acc;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10px 15px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #005f99;
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: #004e7a;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 5px #007acc;
|
||||
}
|
||||
|
||||
#linkForm, #fileForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#linkForm input[type="text"], #fileForm input[type="file"] {
|
||||
width: 80%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
margin-top: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
55
static/lockout.css
Normal file
55
static/lockout.css
Normal file
@@ -0,0 +1,55 @@
|
||||
/* static/lockout.css */
|
||||
body {
|
||||
background-color: #f8d7da;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #721c24;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 30px 40px;
|
||||
border: 2px solid #f5c6cb;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
max-width: 600px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #721c24;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #856404;
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.contact {
|
||||
margin-top: 20px;
|
||||
color: #004085;
|
||||
background-color: #cce5ff;
|
||||
border: 1px solid #b8daff;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.contact a {
|
||||
color: #004085;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.contact a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
/* Centering the login container */
|
||||
body {
|
||||
background-color: #f0f0f0; /* Light grey background for the whole page */
|
||||
background-image: url('/static/eye.jpeg');
|
||||
background-size: cover; /* Ensures the image covers the entire background */
|
||||
background-position: center; /* Centers the image */
|
||||
background-repeat: no-repeat; /* Prevents the background from repeating */
|
||||
background-color: #f0f0f0; /* Fallback color if the image doesn't load */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
102
static/uploads.css
Normal file
102
static/uploads.css
Normal file
@@ -0,0 +1,102 @@
|
||||
/* static/uploads.css */
|
||||
body {
|
||||
background-color: #f0f4f8;
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
background-color: #ffffff;
|
||||
padding: 30px 40px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
max-width: 700px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #007acc;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #333;
|
||||
margin: 15px 0;
|
||||
font-size: 1.2em;
|
||||
border-bottom: 2px solid #007acc;
|
||||
display: inline-block;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background-color: #f9f9f9;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
li:nth-child(odd) {
|
||||
background-color: #eef3f7;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #007acc;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #005f99;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 5px #007acc;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007acc;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #005f99;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
background-color: #007acc;
|
||||
color: #fff;
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
background-color: #005f99;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Error</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='error.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>An error occurred</h1>
|
||||
<p>{{ error_message }}</p>
|
||||
<a href="/">Go back to the main page</a>
|
||||
<div class="error-container">
|
||||
<h1>An error occurred</h1>
|
||||
<p>{{ error_message }}</p>
|
||||
<a href="/">Go back to the main page</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -5,26 +5,29 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>iPhone-Linux Transfer</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>iPhone-Linux Transfer Service</h1>
|
||||
<div class="container">
|
||||
<h1>iPhone-Linux Transfer Service</h1>
|
||||
|
||||
<!-- Upload HTML link form -->
|
||||
<h2>Upload HTML Link</h2>
|
||||
<form id="linkForm" action="/upload/link" method="POST">
|
||||
<input type="text" name="link" placeholder="Enter your link here" required>
|
||||
<button type="submit">Submit Link</button>
|
||||
</form>
|
||||
<!-- Upload HTML link form -->
|
||||
<h2>Upload HTML Link</h2>
|
||||
<form id="linkForm" action="/upload/link" method="POST">
|
||||
<input type="text" name="link" placeholder="Enter your link here" required>
|
||||
<button type="submit">Submit Link</button>
|
||||
</form>
|
||||
|
||||
<!-- Upload file form -->
|
||||
<h2>Upload Image/File</h2>
|
||||
<form id="fileForm" action="/upload/file" method="POST" enctype="multipart/form-data">
|
||||
<input type="file" name="file" required>
|
||||
<button type="submit">Upload File</button>
|
||||
</form>
|
||||
<!-- Upload file form -->
|
||||
<h2>Upload Image/File</h2>
|
||||
<form id="fileForm" action="/upload/files" method="POST" enctype="multipart/form-data">
|
||||
<input type="file" name="files[]" multiple required>
|
||||
<button type="submit">Upload File</button>
|
||||
</form>
|
||||
|
||||
<!-- Button to view uploaded content -->
|
||||
<h2>View Uploaded Links and Images</h2>
|
||||
<button onclick="window.location.href='/uploads';">View Uploaded Content</button>
|
||||
<!-- Button to view uploaded content -->
|
||||
<h2>View Uploaded Links and Images</h2>
|
||||
<button class="view-btn" onclick="window.location.href='/uploads';">View Uploaded Content</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
<!-- templates/lockout.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Account Locked Out</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='lockout.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<h1>You have been locked out due to too many failed login attempts.</h1>
|
||||
<p>If you believe this is an error, please contact the administrator.</p>
|
||||
<div class="container">
|
||||
<h1>Account Locked Out</h1>
|
||||
<p>You have been locked out due to too many failed login attempts.</p>
|
||||
<div class="contact">
|
||||
<p>If you believe this is an error, please contact the administrator.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<!-- templates/uploads.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Uploaded Data</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='uploads.css') }}">
|
||||
<script>
|
||||
// Function to download an item
|
||||
function downloadItem(url) {
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
@@ -27,81 +28,141 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Function to confirm deletion
|
||||
function confirmDelete(url) {
|
||||
if (confirm("Are you sure you want to delete this item?")) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to rename a file
|
||||
function renameItem(uploadId) {
|
||||
const newName = prompt("Enter new name for the file:");
|
||||
if (newName) {
|
||||
fetch(`/rename/${uploadId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ new_name: newName }),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Failed to rename');
|
||||
alert('File renamed successfully');
|
||||
location.reload(); // Reload the page to show updated names
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error renaming file:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Function to preview a file
|
||||
function previewItem(uploadId) {
|
||||
window.open(`/preview/${uploadId}`, '_blank');
|
||||
}
|
||||
|
||||
// Function to download all files of a certain type
|
||||
function downloadAll(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Uploaded Data</h1>
|
||||
<div class="container">
|
||||
<h1>Uploaded Data</h1>
|
||||
|
||||
<!-- Links Section -->
|
||||
<h2>Links</h2>
|
||||
{% if links %}
|
||||
<ul>
|
||||
{% for link in links %}
|
||||
<li>
|
||||
<a href="{{ link[3] }}" target="_blank">{{ link[3] }}</a>
|
||||
(Uploaded by: {{ link[1] }})
|
||||
<button onclick="downloadItem('/download_link/{{ link[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_link/{{ link[0] }}');">Delete</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No links uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Links Section -->
|
||||
<h2>Links</h2>
|
||||
{% if links %}
|
||||
<ul>
|
||||
{% for link in links %}
|
||||
<li>
|
||||
<span>
|
||||
<a href="{{ link[3] }}" target="_blank">{{ link[3] }}</a>
|
||||
(Uploaded by: {{ link[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="downloadItem('/download_link/{{ link[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_link/{{ link[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<button onclick="downloadAll('/download_all_links');">Download All Links</button>
|
||||
{% else %}
|
||||
<p>No links uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Videos Section -->
|
||||
<h2>Videos</h2>
|
||||
{% if videos %}
|
||||
<ul>
|
||||
{% for video in videos %}
|
||||
<li>
|
||||
{{ video[3] }} (Uploaded by: {{ video[1] }})
|
||||
<button onclick="downloadItem('/download/{{ video[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ video[0] }}');">Delete</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No videos uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Videos Section -->
|
||||
<h2>Videos</h2>
|
||||
{% if videos %}
|
||||
<ul>
|
||||
{% for video in videos %}
|
||||
<li>
|
||||
<span>
|
||||
{{ video[3] }} (Uploaded by: {{ video[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ video[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ video[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ video[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ video[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<button onclick="downloadAll('/download_all_videos');">Download All Videos</button>
|
||||
{% else %}
|
||||
<p>No videos uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Photos Section -->
|
||||
<h2>Photos</h2>
|
||||
{% if photos %}
|
||||
<ul>
|
||||
{% for photo in photos %}
|
||||
<li>
|
||||
{{ photo[3] }} (Uploaded by: {{ photo[1] }})
|
||||
<button onclick="downloadItem('/download/{{ photo[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ photo[0] }}');">Delete</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No photos uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Photos Section -->
|
||||
<h2>Photos</h2>
|
||||
{% if photos %}
|
||||
<ul>
|
||||
{% for photo in photos %}
|
||||
<li>
|
||||
<span>
|
||||
{{ photo[3] }} (Uploaded by: {{ photo[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ photo[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ photo[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ photo[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ photo[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No photos uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Miscellaneous Files Section -->
|
||||
<h2>Miscellaneous Files</h2>
|
||||
{% if misc %}
|
||||
<ul>
|
||||
{% for item in misc %}
|
||||
<li>
|
||||
{{ item[3] }} (Uploaded by: {{ item[1] }})
|
||||
<button onclick="downloadItem('/download/{{ item[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ item[0] }}');">Delete</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No miscellaneous files uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Miscellaneous Files Section -->
|
||||
<h2>Miscellaneous Files</h2>
|
||||
{% if misc %}
|
||||
<ul>
|
||||
{% for item in misc %}
|
||||
<li>
|
||||
<span>
|
||||
{{ item[3] }} (Uploaded by: {{ item[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ item[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ item[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ item[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ item[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<button onclick="downloadAll('/download_all_misc');">Download All Misc Files</button>
|
||||
{% else %}
|
||||
<p>No miscellaneous files uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<a href="/">Back to Upload Page</a>
|
||||
<a class="back-link" href="/">Back to Upload Page</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,78 +1,160 @@
|
||||
<!-- templates/uploads.html -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Uploaded Data</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='uploads.css') }}">
|
||||
<script>
|
||||
// Function to download an item
|
||||
function downloadItem(url) {
|
||||
fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Network response was not ok');
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
const downloadUrl = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = downloadUrl;
|
||||
a.download = url.split('/').pop(); // This sets the downloaded file name
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
URL.revokeObjectURL(downloadUrl); // Clean up URL object
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Download failed:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to confirm deletion
|
||||
function confirmDelete(url) {
|
||||
if (confirm("Are you sure you want to delete this item?")) {
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to rename a file
|
||||
function renameItem(uploadId) {
|
||||
const newName = prompt("Enter new name for the file:");
|
||||
if (newName) {
|
||||
fetch(`/rename/${uploadId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ new_name: newName }),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) throw new Error('Failed to rename');
|
||||
alert('File renamed successfully');
|
||||
location.reload(); // Reload the page to show updated names
|
||||
})
|
||||
.catch(error => {
|
||||
alert('Error renaming file:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Function to preview a file
|
||||
function previewItem(uploadId) {
|
||||
window.open(`/preview/${uploadId}`, '_blank');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Uploaded Data</h1>
|
||||
<div class="container">
|
||||
<h1>Uploaded Data</h1>
|
||||
|
||||
<!-- Links Section -->
|
||||
<h2>Links</h2>
|
||||
{% if links %}
|
||||
{% if links|length > 1 %}
|
||||
<button onclick="window.location.href='/download_all_links';">Download All Links</button>
|
||||
<!-- Links Section -->
|
||||
<h2>Links</h2>
|
||||
{% if links %}
|
||||
<ul>
|
||||
{% for link in links %}
|
||||
<li>
|
||||
<span>
|
||||
<a href="{{ link[3] }}" target="_blank">{{ link[3] }}</a>
|
||||
(Uploaded by: {{ link[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="downloadItem('/download_link/{{ link[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_link/{{ link[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No links uploaded.</p>
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% for link in links %}
|
||||
<li>
|
||||
<a href="{{ link[3] }}" target="_blank">{{ link[3] }}</a>
|
||||
(Uploaded by: {{ link[1] }})
|
||||
<button onclick="window.location.href='/download_link/{{ link[0] }}';">Download</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No links uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Videos Section -->
|
||||
<h2>Videos</h2>
|
||||
{% if videos %}
|
||||
<ul>
|
||||
{% for video in videos %}
|
||||
<li>
|
||||
{{ video[3] }} (Uploaded by: {{ video[1] }})
|
||||
<button onclick="window.location.href='/download/{{ video[0] }}';">Download</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No videos uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Videos Section -->
|
||||
<h2>Videos</h2>
|
||||
{% if videos %}
|
||||
<ul>
|
||||
{% for video in videos %}
|
||||
<li>
|
||||
<span>
|
||||
{{ video[3] }} (Uploaded by: {{ video[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ video[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ video[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ video[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ video[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No videos uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Photos Section -->
|
||||
<h2>Photos</h2>
|
||||
{% if photos %}
|
||||
<ul>
|
||||
{% for photo in photos %}
|
||||
<li>
|
||||
{{ photo[3] }} (Uploaded by: {{ photo[1] }})
|
||||
<button onclick="window.location.href='/download/{{ photo[0] }}';">Download</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No photos uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Photos Section -->
|
||||
<h2>Photos</h2>
|
||||
{% if photos %}
|
||||
<ul>
|
||||
{% for photo in photos %}
|
||||
<li>
|
||||
<span>
|
||||
{{ photo[3] }} (Uploaded by: {{ photo[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ photo[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ photo[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ photo[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ photo[0] }}');">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No photos uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<!-- Miscellaneous Files Section -->
|
||||
<h2>Miscellaneous Files</h2>
|
||||
{% if misc %}
|
||||
<ul>
|
||||
{% for item in misc %}
|
||||
<li>
|
||||
{{ item[3] }} (Uploaded by: {{ item[1] }})
|
||||
<button onclick="window.location.href='/download/{{ item[0] }}';">Download</button>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No miscellaneous files uploaded.</p>
|
||||
{% endif %}
|
||||
<!-- Miscellaneous Files Section -->
|
||||
<h2>Miscellaneous Files</h2>
|
||||
{% if misc %}
|
||||
<ul>
|
||||
{% for item in misc %}
|
||||
<li>
|
||||
<span>
|
||||
{{ item[3] }} (Uploaded by: {{ item[1] }})
|
||||
</span>
|
||||
<div>
|
||||
<button onclick="previewItem({{ item[0] }});">Preview</button>
|
||||
<button onclick="renameItem({{ item[0] }});">Rename</button>
|
||||
<button onclick="downloadItem('/download/{{ item[0] }}');">Download</button>
|
||||
<button onclick="confirmDelete('/delete_file/{{ item[0] }});">Delete</button>
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No miscellaneous files uploaded.</p>
|
||||
{% endif %}
|
||||
|
||||
<a href="/">Back to Upload Page</a>
|
||||
<a class="back-link" href="/">Back to Upload Page</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user