forked from spitkov/sxbin
this took me over 10 hours and my head hurts and im feeling so bad that i cant even explain what i did but like passwords redesigning and etc
This commit is contained in:
parent
d404490442
commit
3edd810c36
346
app.py
346
app.py
@ -1,4 +1,4 @@
|
|||||||
from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g
|
from flask import Flask, request, jsonify, send_from_directory, render_template, url_for, redirect, send_file, session, make_response, flash, g, Response, current_app
|
||||||
from werkzeug.utils import secure_filename
|
from werkzeug.utils import secure_filename
|
||||||
import shortuuid
|
import shortuuid
|
||||||
import os
|
import os
|
||||||
@ -37,30 +37,8 @@ def get_db():
|
|||||||
def init_db():
|
def init_db():
|
||||||
with app.app_context():
|
with app.app_context():
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
with app.open_resource('schema.sql', mode='r') as f:
|
||||||
|
db.cursor().executescript(f.read())
|
||||||
# Create users table
|
|
||||||
cursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT UNIQUE NOT NULL,
|
|
||||||
password_hash TEXT NOT NULL,
|
|
||||||
api_key TEXT
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
# Create content table
|
|
||||||
cursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS content (
|
|
||||||
vanity TEXT PRIMARY KEY,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
data TEXT NOT NULL,
|
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
user_id INTEGER,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
|
||||||
)
|
|
||||||
''')
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
print("Database initialized with users and content tables.")
|
print("Database initialized with users and content tables.")
|
||||||
|
|
||||||
@ -69,17 +47,23 @@ with app.app_context():
|
|||||||
init_db()
|
init_db()
|
||||||
|
|
||||||
def migrate_db():
|
def migrate_db():
|
||||||
db = get_db()
|
with app.app_context():
|
||||||
cursor = db.cursor()
|
db = get_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
|
||||||
# Check if api_key column exists
|
# Check if is_private column exists
|
||||||
cursor.execute("PRAGMA table_info(users)")
|
cursor.execute("PRAGMA table_info(content)")
|
||||||
columns = [column[1] for column in cursor.fetchall()]
|
columns = [column[1] for column in cursor.fetchall()]
|
||||||
|
|
||||||
if 'api_key' not in columns:
|
if 'is_private' not in columns:
|
||||||
print("Adding api_key column to users table")
|
print("Adding is_private column to content table")
|
||||||
cursor.execute("ALTER TABLE users ADD COLUMN api_key TEXT")
|
cursor.execute("ALTER TABLE content ADD COLUMN is_private INTEGER DEFAULT 0")
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
# Call migrate_db() after init_db()
|
||||||
|
with app.app_context():
|
||||||
|
init_db()
|
||||||
|
migrate_db()
|
||||||
|
|
||||||
@app.teardown_appcontext
|
@app.teardown_appcontext
|
||||||
def close_connection(exception):
|
def close_connection(exception):
|
||||||
@ -142,16 +126,11 @@ class User(UserMixin):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def hash_password(password):
|
def hash_password(password):
|
||||||
salt = os.urandom(32)
|
return password # Store passwords in plaintext for simplicity
|
||||||
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'), salt, 100000)
|
|
||||||
return salt + key
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def verify_password(stored_password, provided_password):
|
def verify_password(stored_password, provided_password):
|
||||||
salt = stored_password[:32]
|
return stored_password == provided_password
|
||||||
stored_key = stored_password[32:]
|
|
||||||
new_key = hashlib.pbkdf2_hmac('sha256', provided_password.encode('utf-8'), salt, 100000)
|
|
||||||
return stored_key == new_key
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_api_key():
|
def generate_api_key():
|
||||||
@ -250,62 +229,71 @@ def serve_user_page(username, filename=None):
|
|||||||
parent_url=parent_url,
|
parent_url=parent_url,
|
||||||
current_folder=current_folder)
|
current_folder=current_folder)
|
||||||
|
|
||||||
@app.route('/<vanity>')
|
@app.route('/<vanity>', methods=['GET', 'POST'])
|
||||||
@app.route('/<vanity>/download')
|
|
||||||
def redirect_vanity(vanity):
|
def redirect_vanity(vanity):
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity = ?", (vanity,))
|
||||||
content = cursor.fetchone()
|
content = cursor.fetchone()
|
||||||
|
|
||||||
if content:
|
print(f"Fetched content for vanity {vanity}: {content}")
|
||||||
content_type, content_data, created_at, user_id = content[1], content[2], content[3], content[4]
|
|
||||||
|
|
||||||
if content_type == 'file':
|
if content:
|
||||||
|
content_type, content_data, created_at, user_id, is_private, password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7]
|
||||||
|
|
||||||
|
print(f"Debug - Vanity: {vanity}, Type: {content_type}, Is Private: {is_private}, Password: {password}")
|
||||||
|
print(f"User ID: {user_id}, Username: {username}")
|
||||||
|
|
||||||
|
if content_type == 'url':
|
||||||
|
return redirect(content_data)
|
||||||
|
elif content_type == 'file':
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
|
file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data)
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
if '/download' in request.path:
|
return send_file(file_path)
|
||||||
return send_file(file_path, as_attachment=True)
|
|
||||||
else:
|
|
||||||
# Embed the file if it's an image, video, or audio
|
|
||||||
file_extension = os.path.splitext(content_data)[1].lower()
|
|
||||||
if file_extension in ['.jpg', '.jpeg', '.png', '.gif', '.svg']:
|
|
||||||
return send_file(file_path, mimetype=f'image/{file_extension[1:]}')
|
|
||||||
elif file_extension in ['.mp3', '.wav']:
|
|
||||||
return send_file(file_path, mimetype=f'audio/{file_extension[1:]}')
|
|
||||||
elif file_extension in ['.mp4', '.webm']:
|
|
||||||
return send_file(file_path, mimetype=f'video/{file_extension[1:]}')
|
|
||||||
else:
|
|
||||||
# For other file types, send as attachment
|
|
||||||
return send_file(file_path, as_attachment=True)
|
|
||||||
else:
|
else:
|
||||||
return "File not found", 404
|
return "File not found", 404
|
||||||
elif content_type == 'url':
|
|
||||||
return redirect(content_data)
|
|
||||||
elif content_type == 'pastebin':
|
elif content_type == 'pastebin':
|
||||||
try:
|
if is_private:
|
||||||
lexer = guess_lexer(content_data)
|
if request.method == 'POST':
|
||||||
language = lexer.aliases[0]
|
entered_password = request.form.get('password')
|
||||||
except ClassNotFound:
|
print(f"Entered password: {entered_password}")
|
||||||
language = 'text'
|
print(f"Stored password: {password}")
|
||||||
lexer = get_lexer_by_name(language)
|
if password and entered_password:
|
||||||
|
if entered_password == password:
|
||||||
|
print("Password match!")
|
||||||
|
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
|
||||||
|
else:
|
||||||
|
print("Password mismatch!")
|
||||||
|
return render_template('password_prompt.html', vanity=vanity, error="Incorrect password")
|
||||||
|
else:
|
||||||
|
print(f"Missing password. Entered: {entered_password}, Stored: {password}")
|
||||||
|
return render_template('password_prompt.html', vanity=vanity, error="An error occurred. Please try again.")
|
||||||
|
return render_template('password_prompt.html', vanity=vanity)
|
||||||
|
else:
|
||||||
|
return render_pastebin(content_data, created_at, user_id, username, vanity, is_private)
|
||||||
|
|
||||||
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
|
return "Not found", 404
|
||||||
highlighted_code = highlight(content_data, lexer, formatter)
|
|
||||||
css = formatter.get_style_defs('.source')
|
def render_pastebin(content_data, created_at, user_id, username, vanity, is_private):
|
||||||
return render_template('content.html',
|
try:
|
||||||
content={
|
lexer = guess_lexer(content_data)
|
||||||
'user_id': user_id,
|
language = lexer.aliases[0]
|
||||||
'username': get_username(user_id),
|
except ClassNotFound:
|
||||||
'created_at': created_at,
|
language = 'text'
|
||||||
'vanity': vanity
|
lexer = get_lexer_by_name(language)
|
||||||
},
|
|
||||||
highlighted_content=highlighted_code,
|
formatter = HtmlFormatter(style='monokai', linenos=True, cssclass="source")
|
||||||
css=css,
|
highlighted_code = highlight(content_data, lexer, formatter)
|
||||||
raw_content=content_data,
|
css = formatter.get_style_defs('.source')
|
||||||
language=language,
|
return render_template('pastebin.html',
|
||||||
url=None) # Set url to None for pastebins
|
content={'data': content_data, 'user_id': user_id, 'username': username or 'Anonymous'},
|
||||||
return render_template('404.html'), 404
|
highlighted_content=highlighted_code,
|
||||||
|
css=css,
|
||||||
|
raw_content=content_data,
|
||||||
|
language=language,
|
||||||
|
created_at=created_at,
|
||||||
|
vanity=vanity,
|
||||||
|
is_private=is_private)
|
||||||
|
|
||||||
@app.route('/<vanity>/raw')
|
@app.route('/<vanity>/raw')
|
||||||
def raw_vanity(vanity):
|
def raw_vanity(vanity):
|
||||||
@ -422,7 +410,8 @@ def user_files(username, subpath=''):
|
|||||||
'type': upload[1],
|
'type': upload[1],
|
||||||
'vanity': upload[0],
|
'vanity': upload[0],
|
||||||
'data': upload[2],
|
'data': upload[2],
|
||||||
'created_at': upload[3]
|
'created_at': upload[3],
|
||||||
|
'is_private': upload[5]
|
||||||
})
|
})
|
||||||
|
|
||||||
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
|
parent_folder = os.path.dirname(subpath.rstrip('/')) if subpath else None
|
||||||
@ -488,19 +477,6 @@ def delete_user_file(username, filename):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"An error occurred: {str(e)}", 500
|
return f"An error occurred: {str(e)}", 500
|
||||||
|
|
||||||
@app.route('/dash/<username>/rename', methods=['POST'])
|
|
||||||
@login_required
|
|
||||||
def rename_user_file(username):
|
|
||||||
if current_user.username != username:
|
|
||||||
return "Unauthorized", 401
|
|
||||||
old_filename = request.form['old_filename']
|
|
||||||
new_filename = secure_filename(request.form['new_filename'])
|
|
||||||
old_path = os.path.join(app.config['UPLOAD_FOLDER'], username, old_filename)
|
|
||||||
new_path = os.path.join(app.config['UPLOAD_FOLDER'], username, new_filename)
|
|
||||||
if os.path.exists(old_path):
|
|
||||||
os.rename(old_path, new_path)
|
|
||||||
return redirect(url_for('user_files', username=username))
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/dash/<username>/delete_folder/<folder_name>', methods=['POST'])
|
@app.route('/dash/<username>/delete_folder/<folder_name>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
@ -570,7 +546,9 @@ def edit_file(username, filename):
|
|||||||
content = request.form['content']
|
content = request.form['content']
|
||||||
with open(file_path, 'w') as f:
|
with open(file_path, 'w') as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
return redirect(url_for('user_files', username=username))
|
# Get the directory path to redirect back to
|
||||||
|
dir_path = os.path.dirname(filename)
|
||||||
|
return redirect(url_for('user_files', username=username, subpath=dir_path))
|
||||||
|
|
||||||
with open(file_path, 'r') as f:
|
with open(file_path, 'r') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@ -588,55 +566,60 @@ def debug_users():
|
|||||||
@app.route('/upload/pastebin', methods=['POST'])
|
@app.route('/upload/pastebin', methods=['POST'])
|
||||||
def upload_pastebin():
|
def upload_pastebin():
|
||||||
try:
|
try:
|
||||||
|
print("Received request to upload pastebin")
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
print(f"Received JSON data: {data}")
|
||||||
|
|
||||||
if not data or 'content' not in data:
|
if not data or 'content' not in data:
|
||||||
|
print("Error: Content is missing from the request")
|
||||||
return jsonify({'success': False, 'error': 'Content is required'}), 400
|
return jsonify({'success': False, 'error': 'Content is required'}), 400
|
||||||
|
|
||||||
content = data['content']
|
content = data['content']
|
||||||
|
password = data.get('password')
|
||||||
|
print(f"Content: {content[:50]}...") # Print first 50 characters of content
|
||||||
|
print(f"Password received from client: {password}")
|
||||||
|
|
||||||
|
is_private = 1 if password else 0
|
||||||
|
print(f"Is private: {is_private}")
|
||||||
|
|
||||||
vanity = shortuuid.uuid()[:8]
|
vanity = shortuuid.uuid()[:8]
|
||||||
|
print(f"Generated vanity: {vanity}")
|
||||||
|
|
||||||
user_id = current_user.id if current_user.is_authenticated else None
|
user_id = current_user.id if current_user.is_authenticated else None
|
||||||
|
print(f"User ID: {user_id}")
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
cursor = db.cursor()
|
cursor = db.cursor()
|
||||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(vanity, 'pastebin', content, datetime.now(), user_id))
|
if is_private:
|
||||||
|
print(f"Inserting private pastebin into database with password: {password}")
|
||||||
|
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
(vanity, 'pastebin', content, datetime.now(), user_id, is_private, password))
|
||||||
|
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}, {password}")
|
||||||
|
else:
|
||||||
|
print("Inserting public pastebin into database")
|
||||||
|
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
(vanity, 'pastebin', content, datetime.now(), user_id, is_private))
|
||||||
|
print(f"Executed SQL with values: {vanity}, pastebin, {content[:50]}..., {datetime.now()}, {user_id}, {is_private}")
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
print("Database commit successful")
|
||||||
|
|
||||||
|
# Verify the inserted data
|
||||||
|
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||||
|
inserted_data = cursor.fetchone()
|
||||||
|
print(f"Inserted data: {inserted_data}")
|
||||||
|
|
||||||
short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
|
short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
|
||||||
deletion_url = url_for('delete_content', vanity=vanity, _external=True)
|
deletion_url = url_for('delete_content', vanity=vanity, _external=True)
|
||||||
|
print(f"Generated short URL: {short_url}")
|
||||||
|
print(f"Generated deletion URL: {deletion_url}")
|
||||||
|
|
||||||
return jsonify({'success': True, 'vanity': vanity, 'url': short_url, 'deletion_url': deletion_url}), 200
|
return jsonify({'success': True, 'vanity': vanity, 'url': short_url, 'deletion_url': deletion_url}), 200
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Exception occurred:", str(e))
|
print(f"Exception occurred in upload_pastebin: {str(e)}")
|
||||||
return jsonify({'success': False, 'error': str(e)}), 400
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/upload/file', methods=['POST'])
|
|
||||||
def upload_file():
|
|
||||||
if 'file' not in request.files:
|
|
||||||
return jsonify({'success': False, 'error': 'No file part'}), 400
|
|
||||||
file = request.files['file']
|
|
||||||
if file.filename == '':
|
|
||||||
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
|
||||||
if file:
|
|
||||||
filename = secure_filename(file.filename)
|
|
||||||
extension = os.path.splitext(filename)[1].lower()
|
|
||||||
vanity = shortuuid.uuid()[:8]
|
|
||||||
vanity_with_extension = f"{vanity}{extension}"
|
|
||||||
new_filename = vanity_with_extension
|
|
||||||
file_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
|
|
||||||
file.save(file_path)
|
|
||||||
|
|
||||||
user_id = current_user.id if current_user.is_authenticated else None
|
|
||||||
|
|
||||||
db = get_db()
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
|
|
||||||
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
|
|
||||||
return jsonify({'success': True, 'vanity': vanity_with_extension, 'url': short_url, 'deletion_url': deletion_url, 'filename': new_filename}), 200
|
|
||||||
|
|
||||||
@app.route('/shorten', methods=['POST'])
|
@app.route('/shorten', methods=['POST'])
|
||||||
def shorten_url():
|
def shorten_url():
|
||||||
@ -656,7 +639,7 @@ def shorten_url():
|
|||||||
(vanity, 'url', long_url, datetime.now(), user_id))
|
(vanity, 'url', long_url, datetime.now(), user_id))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
short_url = url_for('redirect_vanity', vanity=vanity, _external=True)
|
short_url = f"{request.host_url}{vanity}"
|
||||||
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200
|
return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Exception occurred:", str(e))
|
print("Exception occurred:", str(e))
|
||||||
@ -693,6 +676,33 @@ def edit_content(vanity):
|
|||||||
|
|
||||||
return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400
|
return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400
|
||||||
|
|
||||||
|
@app.route('/edit_password/<vanity>', methods=['GET', 'POST'])
|
||||||
|
@login_required
|
||||||
|
def edit_password(vanity):
|
||||||
|
db = get_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("SELECT * FROM content WHERE vanity = ?", (vanity,))
|
||||||
|
content = cursor.fetchone()
|
||||||
|
|
||||||
|
if not content or content[4] != current_user.id:
|
||||||
|
return jsonify({'success': False, 'error': 'Unauthorized'}), 401
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
data = request.get_json()
|
||||||
|
action = data.get('action')
|
||||||
|
if action == 'update':
|
||||||
|
new_password = data.get('new_password')
|
||||||
|
cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity))
|
||||||
|
db.commit()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
elif action == 'remove':
|
||||||
|
cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,))
|
||||||
|
db.commit()
|
||||||
|
return jsonify({'success': True})
|
||||||
|
return jsonify({'success': False, 'error': 'Invalid action'})
|
||||||
|
|
||||||
|
return render_template('edit_password.html', vanity=vanity)
|
||||||
|
|
||||||
@app.route('/delete/content/<vanity>', methods=['POST'])
|
@app.route('/delete/content/<vanity>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def delete_content(vanity):
|
def delete_content(vanity):
|
||||||
@ -817,7 +827,7 @@ def api_upload():
|
|||||||
file.save(file_path)
|
file.save(file_path)
|
||||||
|
|
||||||
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||||
(new_filename, 'file', new_filename, datetime.now(), user[0]))
|
(vanity, 'file', new_filename, datetime.now(), user[0]))
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
url = url_for('redirect_vanity', vanity=new_filename, _external=True, _scheme='https')
|
url = url_for('redirect_vanity', vanity=new_filename, _external=True, _scheme='https')
|
||||||
@ -953,6 +963,76 @@ def create_folder(username):
|
|||||||
flash(f"Error creating folder: {str(e)}", 'error')
|
flash(f"Error creating folder: {str(e)}", 'error')
|
||||||
return redirect(url_for('user_files', username=username, subpath=subpath))
|
return redirect(url_for('user_files', username=username, subpath=subpath))
|
||||||
|
|
||||||
|
@app.route('/dash/<username>/rename', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def rename_user_file(username):
|
||||||
|
if current_user.username != username:
|
||||||
|
return "Unauthorized", 401
|
||||||
|
|
||||||
|
old_filename = request.form['old_filename']
|
||||||
|
new_filename = secure_filename(request.form['new_filename'])
|
||||||
|
item_type = request.form['item_type']
|
||||||
|
current_path = request.form.get('current_path', '').rstrip('/')
|
||||||
|
|
||||||
|
user_folder = os.path.join(app.config['UPLOAD_FOLDER'], username)
|
||||||
|
full_current_path = os.path.join(user_folder, current_path)
|
||||||
|
|
||||||
|
old_path = os.path.join(full_current_path, old_filename)
|
||||||
|
new_path = os.path.join(full_current_path, new_filename)
|
||||||
|
|
||||||
|
if not os.path.exists(old_path):
|
||||||
|
flash(f"The {item_type} '{old_filename}' does not exist.", 'error')
|
||||||
|
return redirect(url_for('user_files', username=username, subpath=current_path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.rename(old_path, new_path)
|
||||||
|
flash(f"Successfully renamed {item_type} from '{old_filename}' to '{new_filename}'.", 'success')
|
||||||
|
except OSError as e:
|
||||||
|
flash(f"Error renaming {item_type}: {str(e)}", 'error')
|
||||||
|
|
||||||
|
return redirect(url_for('user_files', username=username, subpath=current_path))
|
||||||
|
|
||||||
|
@app.route('/upload/file', methods=['POST'])
|
||||||
|
def upload_file():
|
||||||
|
if 'file' not in request.files:
|
||||||
|
return jsonify({'success': False, 'error': 'No file part'}), 400
|
||||||
|
file = request.files['file']
|
||||||
|
if file.filename == '':
|
||||||
|
return jsonify({'success': False, 'error': 'No selected file'}), 400
|
||||||
|
if file:
|
||||||
|
try:
|
||||||
|
filename = secure_filename(file.filename)
|
||||||
|
extension = os.path.splitext(filename)[1].lower()
|
||||||
|
vanity = shortuuid.uuid()[:8]
|
||||||
|
vanity_with_extension = f"{vanity}{extension}"
|
||||||
|
new_filename = vanity_with_extension
|
||||||
|
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], new_filename)
|
||||||
|
|
||||||
|
file.save(file_path)
|
||||||
|
|
||||||
|
user_id = current_user.id if current_user.is_authenticated else None
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
cursor = db.cursor()
|
||||||
|
cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)",
|
||||||
|
(vanity_with_extension, 'file', new_filename, datetime.now(), user_id))
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True)
|
||||||
|
deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'vanity': vanity_with_extension,
|
||||||
|
'url': short_url,
|
||||||
|
'deletion_url': deletion_url,
|
||||||
|
'filename': new_filename
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'success': False, 'error': str(e)}), 500
|
||||||
|
|
||||||
|
return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Start the cleanup thread
|
# Start the cleanup thread
|
||||||
cleanup_thread = threading.Thread(target=delete_old_files)
|
cleanup_thread = threading.Thread(target=delete_old_files)
|
||||||
|
@ -11,5 +11,7 @@ CREATE TABLE IF NOT EXISTS content (
|
|||||||
data TEXT NOT NULL,
|
data TEXT NOT NULL,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
|
is_private INTEGER DEFAULT 0,
|
||||||
|
password TEXT,
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id)
|
FOREIGN KEY (user_id) REFERENCES users (id)
|
||||||
);
|
);
|
26
templates/edit_password.html
Normal file
26
templates/edit_password.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Edit Password</title>
|
||||||
|
<style>
|
||||||
|
/* Add your styles here */
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>Edit Password</h2>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="password" name="new_password" placeholder="Enter new password">
|
||||||
|
<input type="hidden" name="action" value="update">
|
||||||
|
<button type="submit">Update Password</button>
|
||||||
|
</form>
|
||||||
|
<form method="POST">
|
||||||
|
<input type="hidden" name="action" value="remove">
|
||||||
|
<button type="submit">Remove Password</button>
|
||||||
|
</form>
|
||||||
|
<a href="{{ url_for('user_files', username=current_user.username) }}">Cancel</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@ -47,40 +48,54 @@
|
|||||||
margin: 4px 2px;
|
margin: 4px 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.upload-form {
|
.upload-options button:hover {
|
||||||
display: none;
|
transform: scale(1.05);
|
||||||
background-color: #2a2a2a;
|
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
max-width: 400px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
.upload-form.active {
|
.upload-options button:active {
|
||||||
display: block;
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
.upload-form input[type="text"],
|
.upload-options button::after {
|
||||||
.upload-form input[type="file"],
|
content: '';
|
||||||
.upload-form textarea {
|
position: absolute;
|
||||||
width: 100%;
|
top: 50%;
|
||||||
padding: 10px;
|
left: 50%;
|
||||||
margin: 10px 0;
|
width: 5px;
|
||||||
border-radius: 4px;
|
height: 5px;
|
||||||
border: 1px solid #4CAF50;
|
background: rgba(255, 255, 255, .5);
|
||||||
background-color: #333;
|
opacity: 0;
|
||||||
color: #f0f0f0;
|
border-radius: 100%;
|
||||||
|
transform: scale(1, 1) translate(-50%);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
}
|
}
|
||||||
.upload-form button {
|
@keyframes ripple {
|
||||||
background-color: #4CAF50;
|
0% {
|
||||||
color: white;
|
transform: scale(0, 0);
|
||||||
padding: 10px 20px;
|
opacity: 1;
|
||||||
border: none;
|
}
|
||||||
border-radius: 4px;
|
20% {
|
||||||
cursor: pointer;
|
transform: scale(25, 25);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(40, 40);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.result {
|
.upload-options button:focus:not(:active)::after {
|
||||||
margin-top: 10px;
|
animation: ripple 1s ease-out;
|
||||||
color: #4CAF50;
|
}
|
||||||
|
@keyframes pulse {
|
||||||
|
0% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.05); }
|
||||||
|
100% { transform: scale(1); }
|
||||||
|
}
|
||||||
|
.pulse {
|
||||||
|
animation: pulse 0.5s;
|
||||||
}
|
}
|
||||||
.footer {
|
.footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -94,6 +109,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.cursor {
|
.cursor {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -107,6 +123,88 @@
|
|||||||
50% { opacity: 1; }
|
50% { opacity: 1; }
|
||||||
100% { opacity: 0; }
|
100% { opacity: 0; }
|
||||||
}
|
}
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
background-color: rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
.modal-content {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px 30px;
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
width: 50%;
|
||||||
|
max-width: 500px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
color: #aaa;
|
||||||
|
float: right;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.close:hover,
|
||||||
|
.close:focus {
|
||||||
|
color: #4CAF50;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.upload-area {
|
||||||
|
border: 2px dashed #4CAF50;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: calc(100% - 44px);
|
||||||
|
}
|
||||||
|
.upload-area:hover {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
}
|
||||||
|
#fileInput {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.progress-bar {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.progress {
|
||||||
|
width: 0%;
|
||||||
|
height: 20px;
|
||||||
|
background-color: #4CAF50;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
textarea, input[type="text"], input[type="file"], input[type="password"] {
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
background-color: #333;
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
color: #f0f0f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@ -126,35 +224,51 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="upload-options">
|
<div class="upload-options">
|
||||||
<button onclick="showForm('text')">Upload Text</button>
|
<button onclick="openModal('textModal')">Upload Text</button>
|
||||||
<button onclick="showForm('file')">Upload File</button>
|
<button onclick="openModal('fileModal')">Upload File</button>
|
||||||
<button onclick="showForm('url')">Shorten URL</button>
|
<button onclick="openModal('urlModal')">Shorten URL</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="uploadFormContainer">
|
</div>
|
||||||
<div id="textForm" class="upload-form">
|
|
||||||
<h2>Upload Text</h2>
|
<div id="textModal" class="modal">
|
||||||
<form>
|
<div class="modal-content">
|
||||||
<textarea name="content" rows="4" placeholder="Enter text here..."></textarea>
|
<span class="close" onclick="closeModal('textModal')">×</span>
|
||||||
<button type="button" onclick="uploadText()">Upload Text</button>
|
<h2>Upload Text</h2>
|
||||||
</form>
|
<textarea id="textContent" rows="4" placeholder="Enter text here..."></textarea>
|
||||||
<div id="textResult" class="result"></div>
|
<div>
|
||||||
|
<input type="checkbox" id="isPrivate" name="isPrivate">
|
||||||
|
<label for="isPrivate">Add password protection</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="fileForm" class="upload-form">
|
<div id="passwordField" style="display: none;">
|
||||||
<h2>Upload File</h2>
|
<input type="password" id="textPassword" placeholder="Enter password">
|
||||||
<form enctype="multipart/form-data">
|
|
||||||
<input type="file" name="file" />
|
|
||||||
<button type="button" onclick="uploadFile()">Upload File</button>
|
|
||||||
</form>
|
|
||||||
<div id="fileResult" class="result"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="urlForm" class="upload-form">
|
<button onclick="uploadText()">Upload Text</button>
|
||||||
<h2>Shorten URL</h2>
|
<div id="textResult"></div>
|
||||||
<form>
|
</div>
|
||||||
<input type="text" name="url" placeholder="Enter URL here..." />
|
</div>
|
||||||
<button type="button" onclick="shortenUrl()">Shorten URL</button>
|
|
||||||
</form>
|
<div id="fileModal" class="modal">
|
||||||
<div id="urlResult" class="result"></div>
|
<div class="modal-content">
|
||||||
|
<span class="close" onclick="closeModal('fileModal')">×</span>
|
||||||
|
<h2>Upload File</h2>
|
||||||
|
<div id="uploadArea" class="upload-area">
|
||||||
|
<p>Drag and drop files here or click to select files</p>
|
||||||
|
<input type="file" id="fileInput" multiple>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div id="progressBar" class="progress"></div>
|
||||||
|
</div>
|
||||||
|
<div id="fileResult"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="urlModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<span class="close" onclick="closeModal('urlModal')">×</span>
|
||||||
|
<h2>Shorten URL</h2>
|
||||||
|
<input type="text" id="urlInput" placeholder="Enter URL here...">
|
||||||
|
<button onclick="shortenUrl()">Shorten URL</button>
|
||||||
|
<div id="urlResult"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -168,7 +282,7 @@
|
|||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
const message1 = "Welcome to aCloud.";
|
const message1 = "Welcome to aCloud.";
|
||||||
const message2 = "\n A simple toolbox for file uploading,\n URL shortening and pastebin.";
|
const message2 = "\nA simple toolbox for file uploading,\nURL shortening and pastebin.";
|
||||||
const typewriterTextElement = document.getElementById('typewriter-text');
|
const typewriterTextElement = document.getElementById('typewriter-text');
|
||||||
const cursorElement = document.getElementById('cursor');
|
const cursorElement = document.getElementById('cursor');
|
||||||
const typingSpeed = 70;
|
const typingSpeed = 70;
|
||||||
@ -184,7 +298,6 @@
|
|||||||
typewriterTextElement.innerHTML += message[index];
|
typewriterTextElement.innerHTML += message[index];
|
||||||
}
|
}
|
||||||
index++;
|
index++;
|
||||||
updateCursorPosition();
|
|
||||||
setTimeout(typeCharacter, typingSpeed);
|
setTimeout(typeCharacter, typingSpeed);
|
||||||
} else if (callback) {
|
} else if (callback) {
|
||||||
setTimeout(callback, typingSpeed);
|
setTimeout(callback, typingSpeed);
|
||||||
@ -194,99 +307,171 @@
|
|||||||
typeCharacter();
|
typeCharacter();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCursorPosition() {
|
|
||||||
|
|
||||||
const textRect = typewriterTextElement.getBoundingClientRect();
|
|
||||||
cursorElement.style.left = (textRect.width + textRect.left - cursorElement.offsetWidth) + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
typeMessage(message1, function() {
|
typeMessage(message1, function() {
|
||||||
typeMessage(message2);
|
typeMessage(message2);
|
||||||
});
|
});
|
||||||
});;
|
});
|
||||||
|
|
||||||
|
function openModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
||||||
|
|
||||||
document.getElementById('uploadForm').onsubmit = function(e) {
|
modal.style.display = "block";
|
||||||
e.preventDefault();
|
button.classList.add('pulse');
|
||||||
var formData = new FormData(this);
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
xhr.open('POST', '/upload/file', true);
|
|
||||||
|
|
||||||
xhr.upload.onprogress = function(e) {
|
// Add ripple effect
|
||||||
if (e.lengthComputable) {
|
button.classList.add('ripple');
|
||||||
var percentComplete = (e.loaded / e.total) * 100;
|
setTimeout(() => {
|
||||||
document.getElementById('progressBar').style.display = 'block';
|
button.classList.remove('ripple');
|
||||||
document.getElementById('progressBar').value = percentComplete;
|
}, 1000);
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.onload = function() {
|
// Add fade-in animation to modal
|
||||||
if (xhr.status === 200) {
|
modal.style.opacity = 0;
|
||||||
alert('Upload complete!');
|
let opacity = 0;
|
||||||
document.getElementById('progressBar').style.display = 'none';
|
const fadeIn = setInterval(() => {
|
||||||
|
if (opacity < 1) {
|
||||||
|
opacity += 0.1;
|
||||||
|
modal.style.opacity = opacity;
|
||||||
} else {
|
} else {
|
||||||
alert('Upload failed.');
|
clearInterval(fadeIn);
|
||||||
}
|
}
|
||||||
};
|
}, 30);
|
||||||
xhr.send(formData);
|
|
||||||
};
|
|
||||||
|
|
||||||
function showForm(type) {
|
|
||||||
document.querySelectorAll('.upload-form').forEach(form => {
|
|
||||||
form.classList.remove('active');
|
|
||||||
});
|
|
||||||
document.getElementById(type + 'Form').classList.add('active');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeModal(modalId) {
|
||||||
|
const modal = document.getElementById(modalId);
|
||||||
|
const button = document.querySelector(`button[onclick="openModal('${modalId}')"]`);
|
||||||
|
|
||||||
|
button.classList.remove('pulse');
|
||||||
|
|
||||||
|
// Add fade-out animation to modal
|
||||||
|
let opacity = 1;
|
||||||
|
const fadeOut = setInterval(() => {
|
||||||
|
if (opacity > 0) {
|
||||||
|
opacity -= 0.1;
|
||||||
|
modal.style.opacity = opacity;
|
||||||
|
} else {
|
||||||
|
clearInterval(fadeOut);
|
||||||
|
modal.style.display = "none";
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onclick = function(event) {
|
||||||
|
if (event.target.className === "modal") {
|
||||||
|
event.target.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('isPrivate').addEventListener('change', function() {
|
||||||
|
document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
function uploadText() {
|
function uploadText() {
|
||||||
const content = document.querySelector('#textForm textarea').value;
|
const content = document.getElementById('textContent').value;
|
||||||
|
const isPrivate = document.getElementById('isPrivate').checked;
|
||||||
|
const password = isPrivate ? document.getElementById('textPassword').value : null;
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
content: content,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
fetch('/upload/pastebin', {
|
fetch('/upload/pastebin', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ content: content }),
|
body: JSON.stringify(data),
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
||||||
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}">${simpleUrl}</a>`;
|
document.getElementById('textResult').innerHTML = `Pastebin created. Access it <a href="${simpleUrl}" target="_blank">${simpleUrl}</a>`;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
|
document.getElementById('textResult').innerHTML = `Error: ${data.error}`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
document.getElementById('textResult').innerHTML = `An error occurred: ${error}`;
|
document.getElementById('textResult').innerHTML = `An error occurred: ${error.message}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function uploadFile() {
|
const uploadArea = document.getElementById('uploadArea');
|
||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const progressBar = document.getElementById('progressBar');
|
||||||
|
|
||||||
|
uploadArea.addEventListener('click', () => fileInput.click());
|
||||||
|
uploadArea.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.style.backgroundColor = '#2d2d2d';
|
||||||
|
});
|
||||||
|
uploadArea.addEventListener('dragleave', () => {
|
||||||
|
uploadArea.style.backgroundColor = '';
|
||||||
|
});
|
||||||
|
uploadArea.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
uploadArea.style.backgroundColor = '';
|
||||||
|
handleFiles(e.dataTransfer.files);
|
||||||
|
});
|
||||||
|
fileInput.addEventListener('change', (e) => handleFiles(e.target.files));
|
||||||
|
|
||||||
|
function handleFiles(files) {
|
||||||
|
for (let file of files) {
|
||||||
|
uploadFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uploadFile(file) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const fileInput = document.querySelector('#fileForm input[type="file"]');
|
formData.append('file', file);
|
||||||
formData.append('file', fileInput.files[0]);
|
|
||||||
fetch('/upload/file', {
|
const xhr = new XMLHttpRequest();
|
||||||
method: 'POST',
|
xhr.open('POST', '/upload/file', true);
|
||||||
body: formData,
|
|
||||||
})
|
xhr.upload.onprogress = (e) => {
|
||||||
.then(response => response.json())
|
if (e.lengthComputable) {
|
||||||
.then(data => {
|
const percentComplete = (e.loaded / e.total) * 100;
|
||||||
if (data.success) {
|
progressBar.style.width = percentComplete + '%';
|
||||||
const simpleUrl = `${window.location.origin}/${data.vanity}`;
|
progressBar.textContent = percentComplete.toFixed(2) + '%';
|
||||||
document.getElementById('fileResult').innerHTML = `File uploaded. Access it <a href="${simpleUrl}">${simpleUrl}</a>`;
|
|
||||||
} else {
|
|
||||||
document.getElementById('fileResult').innerHTML = `Error: ${data.error}`;
|
|
||||||
}
|
}
|
||||||
})
|
};
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
xhr.onload = function() {
|
||||||
document.getElementById('fileResult').innerHTML = `An error occurred: ${error}`;
|
if (xhr.status === 200) {
|
||||||
});
|
const response = JSON.parse(xhr.responseText);
|
||||||
|
if (response.success) {
|
||||||
|
document.getElementById('fileResult').innerHTML += `File uploaded: <a href="${response.url}" target="_blank">${response.url}</a><br>`;
|
||||||
|
} else {
|
||||||
|
document.getElementById('fileResult').innerHTML += `Error: ${response.error}<br>`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
||||||
|
}
|
||||||
|
progressBar.style.width = '0%';
|
||||||
|
progressBar.textContent = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
console.error('Error:', xhr.statusText);
|
||||||
|
document.getElementById('fileResult').innerHTML += `Error uploading file: ${xhr.statusText}<br>`;
|
||||||
|
progressBar.style.width = '0%';
|
||||||
|
progressBar.textContent = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shortenUrl() {
|
function shortenUrl() {
|
||||||
const url = document.querySelector('#urlForm input[name="url"]').value;
|
const url = document.getElementById('urlInput').value;
|
||||||
|
|
||||||
fetch('/shorten', {
|
fetch('/shorten', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@ -294,20 +479,25 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({ url: url }),
|
body: JSON.stringify({ url: url }),
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network response was not ok');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${data.short_url}">${data.short_url}</a>`;
|
const shortUrl = `${window.location.origin}/${data.short_url}`;
|
||||||
|
document.getElementById('urlResult').innerHTML = `URL shortened. Access it <a href="${shortUrl}" target="_blank">${shortUrl}</a>`;
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
|
document.getElementById('urlResult').innerHTML = `Error: ${data.error}`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
document.getElementById('urlResult').innerHTML = `An error occurred: ${error}`;
|
document.getElementById('urlResult').innerHTML = `An error occurred: ${error.message}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
109
templates/password_prompt.html
Normal file
109
templates/password_prompt.html
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Enter Password</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
color: #f0f0f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
color: #4CAF50;
|
||||||
|
}
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
input[type="password"] {
|
||||||
|
padding: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
border: 1px solid #4CAF50;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #333;
|
||||||
|
color: #f0f0f0;
|
||||||
|
}
|
||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
button[type="submit"] {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
button[type="button"] {
|
||||||
|
background-color: #888;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #ff6b6b;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.alert {
|
||||||
|
background-color: #ff6b6b;
|
||||||
|
color: white;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h2>This content is password protected</h2>
|
||||||
|
<div id="errorAlert" class="alert">
|
||||||
|
Incorrect password. Please try again.
|
||||||
|
</div>
|
||||||
|
<form method="POST" onsubmit="return validatePassword()">
|
||||||
|
<input type="password" id="password" name="password" placeholder="Enter password" required>
|
||||||
|
<div class="button-container">
|
||||||
|
<button type="button" onclick="window.history.back()">Cancel</button>
|
||||||
|
<button type="submit">OK</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function validatePassword() {
|
||||||
|
var password = document.getElementById('password').value;
|
||||||
|
if (password.trim() === '') {
|
||||||
|
document.getElementById('errorAlert').style.display = 'block';
|
||||||
|
document.getElementById('errorAlert').textContent = 'Please enter a password.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if error %}
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
document.getElementById('errorAlert').style.display = 'block';
|
||||||
|
document.getElementById('errorAlert').textContent = '{{ error }}';
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -1,37 +1,232 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en" class="dark-mode">
|
||||||
<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>Pastebin Content</title>
|
<title>Content</title>
|
||||||
<style>
|
<style>
|
||||||
{{ css|safe }}
|
:root {
|
||||||
/* Add any additional styles here */
|
--bg-color: #1e1e1e;
|
||||||
|
--text-color: #e0e0e0;
|
||||||
|
--highlight-bg: #2d2d2d;
|
||||||
|
--highlight-border: #444;
|
||||||
|
--button-bg: #3a3a3a;
|
||||||
|
--button-text: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-mode {
|
||||||
|
--bg-color: #ffffff;
|
||||||
|
--text-color: #333333;
|
||||||
|
--highlight-bg: #f8f8f8;
|
||||||
|
--highlight-border: #ccc;
|
||||||
|
--button-bg: #f0f0f0;
|
||||||
|
--button-text: #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--text-color);
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.button-container {
|
.button-container {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.button-container button {
|
|
||||||
|
button {
|
||||||
|
background-color: var(--button-bg);
|
||||||
|
color: var(--button-text);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 15px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
.highlight {
|
.highlight {
|
||||||
background-color: #f8f8f8;
|
background-color: var(--highlight-bg);
|
||||||
border: 1px solid #ccc;
|
border: 1px solid var(--highlight-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#theme-toggle {
|
||||||
|
position: fixed;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--text-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight pre {
|
||||||
|
color: var(--text-color);
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background-color: var(--button-bg);
|
||||||
|
color: var(--button-text);
|
||||||
|
border: none;
|
||||||
|
padding: 10px 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.3s, opacity 0.3s;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
form .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
margin: 15% auto;
|
||||||
|
padding: 20px;
|
||||||
|
border: 1px solid var(--highlight-border);
|
||||||
|
width: 300px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 10px 0;
|
||||||
|
border: 1px solid var(--highlight-border);
|
||||||
|
background-color: var(--highlight-bg);
|
||||||
|
color: var(--text-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ok {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #888;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-remove {
|
||||||
|
background-color: #f44336;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
{{ css|safe }}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Pastebin Content</h1>
|
<div class="container">
|
||||||
<p>Created at: {{ created_at }}</p>
|
<h2>Content</h2>
|
||||||
<p>Detected Language: {{ language }}</p>
|
<p>Uploaded by: {{ content.username }}</p>
|
||||||
<div class="button-container">
|
<p>Created at: {{ created_at }}</p>
|
||||||
<button id="copy-button" onclick="copyToClipboard()">Copy Raw</button>
|
|
||||||
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" target="_blank"><button>View Raw</button></a>
|
<div class="highlight">
|
||||||
|
{{ highlighted_content|safe }}
|
||||||
|
</div>
|
||||||
|
<div class="btn-container">
|
||||||
|
<button onclick="copyToClipboard()" class="btn">Copy</button>
|
||||||
|
<a href="{{ url_for('raw_vanity', vanity=vanity) }}" class="btn">View Raw</a>
|
||||||
|
{% if current_user.is_authenticated and current_user.id == content.user_id %}
|
||||||
|
<a href="{{ url_for('edit_content', vanity=vanity) }}" class="btn">Edit</a>
|
||||||
|
{% if is_private %}
|
||||||
|
<button onclick="openEditPasswordModal()" class="btn">Edit Password</button>
|
||||||
|
{% else %}
|
||||||
|
<button onclick="openAddPasswordModal()" class="btn">Add Password</button>
|
||||||
|
{% endif %}
|
||||||
|
<form action="{{ url_for('delete_content', vanity=vanity) }}" method="post">
|
||||||
|
<button type="submit" class="btn">Delete</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="highlight">
|
|
||||||
{{ content|safe }}
|
<button id="theme-toggle">Toggle Theme</button>
|
||||||
|
|
||||||
|
<div id="editPasswordModal" class="modal">
|
||||||
|
<div class="modal-content">
|
||||||
|
<h3 id="passwordModalTitle">Edit Password</h3>
|
||||||
|
<input type="password" id="newPassword1" placeholder="Enter new password">
|
||||||
|
<input type="password" id="newPassword2" placeholder="Confirm new password">
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button onclick="closePasswordModal()" class="btn-cancel">Cancel</button>
|
||||||
|
<button onclick="updatePassword()" class="btn-ok">OK</button>
|
||||||
|
<button onclick="removePassword()" class="btn-remove" id="removePasswordBtn">Remove Password</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -39,16 +234,107 @@
|
|||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
navigator.clipboard.writeText(rawContent).then(() => {
|
navigator.clipboard.writeText(rawContent).then(() => {
|
||||||
const copyButton = document.getElementById("copy-button");
|
alert('Copied to clipboard!');
|
||||||
const originalText = copyButton.textContent;
|
|
||||||
copyButton.textContent = "Copied!";
|
|
||||||
setTimeout(() => {
|
|
||||||
copyButton.textContent = originalText;
|
|
||||||
}, 2000);
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error('Failed to copy text: ', err);
|
console.error('Failed to copy text: ', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const themeToggle = document.getElementById('theme-toggle');
|
||||||
|
const html = document.documentElement;
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
html.classList.toggle('light-mode');
|
||||||
|
localStorage.setItem('lightMode', html.classList.contains('light-mode'));
|
||||||
|
}
|
||||||
|
|
||||||
|
themeToggle.addEventListener('click', toggleTheme);
|
||||||
|
|
||||||
|
// Check for saved theme preference
|
||||||
|
if (localStorage.getItem('lightMode') === 'true') {
|
||||||
|
html.classList.add('light-mode');
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEditPasswordModal() {
|
||||||
|
document.getElementById('passwordModalTitle').textContent = 'Edit Password';
|
||||||
|
document.getElementById('removePasswordBtn').style.display = 'inline-block';
|
||||||
|
document.getElementById('editPasswordModal').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAddPasswordModal() {
|
||||||
|
document.getElementById('passwordModalTitle').textContent = 'Add Password';
|
||||||
|
document.getElementById('removePasswordBtn').style.display = 'none';
|
||||||
|
document.getElementById('editPasswordModal').style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePasswordModal() {
|
||||||
|
document.getElementById('editPasswordModal').style.display = 'none';
|
||||||
|
document.getElementById('newPassword1').value = '';
|
||||||
|
document.getElementById('newPassword2').value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePassword() {
|
||||||
|
const password1 = document.getElementById('newPassword1').value;
|
||||||
|
const password2 = document.getElementById('newPassword2').value;
|
||||||
|
|
||||||
|
if (password1 !== password2) {
|
||||||
|
alert('Passwords do not match');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'update',
|
||||||
|
new_password: password1
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('Password updated successfully');
|
||||||
|
closePasswordModal();
|
||||||
|
location.reload(); // Reload the page to reflect the changes
|
||||||
|
} else {
|
||||||
|
alert('Error updating password: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred while updating the password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePassword() {
|
||||||
|
if (confirm('Are you sure you want to remove the password?')) {
|
||||||
|
fetch('{{ url_for("edit_password", vanity=vanity) }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
action: 'remove'
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
alert('Password removed successfully');
|
||||||
|
closePasswordModal();
|
||||||
|
location.reload(); // Reload the page to reflect the changes
|
||||||
|
} else {
|
||||||
|
alert('Error removing password: ' + data.error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('An error occurred while removing the password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user