Push all previous files to repo (26.01.08-LATEST)
This commit is contained in:
11
.gitignore
vendored
11
.gitignore
vendored
@@ -0,0 +1,11 @@
|
|||||||
|
__pycache__/
|
||||||
|
.vscode/
|
||||||
|
.DS_Store
|
||||||
|
logs
|
||||||
|
users.json
|
||||||
|
db.json
|
||||||
|
uploads/
|
||||||
|
uploads/*
|
||||||
|
update_db.py
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
305
app.py
Normal file
305
app.py
Normal file
@@ -0,0 +1,305 @@
|
|||||||
|
from flask import (
|
||||||
|
Flask,
|
||||||
|
render_template,
|
||||||
|
session,
|
||||||
|
request,
|
||||||
|
url_for,
|
||||||
|
send_from_directory,
|
||||||
|
flash
|
||||||
|
)
|
||||||
|
from utils import redirect, dbload, dbsave, udbload, udbsave
|
||||||
|
from werkzeug.exceptions import RequestEntityTooLarge
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import logging.handlers
|
||||||
|
|
||||||
|
|
||||||
|
app = Flask('adrive', static_folder='static', template_folder='templates')
|
||||||
|
app.config['UPLOAD_DIRECTORY'] = 'uploads/'
|
||||||
|
app.config['MAX_CONTENT_LENGTH'] = 1000000 * 1024 * 1024
|
||||||
|
app.config['SECRET_KEY'] = str(random.randint(99999, 9999999))
|
||||||
|
|
||||||
|
# Redirect logs to the file 'logs'
|
||||||
|
handler = logging.handlers.RotatingFileHandler('logs', maxBytes=1024 * 1024)
|
||||||
|
logging.getLogger('werkzeug').setLevel(logging.DEBUG)
|
||||||
|
logging.getLogger('werkzeug').addHandler(handler)
|
||||||
|
app.logger.setLevel(logging.WARNING)
|
||||||
|
app.logger.addHandler(handler)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
|
||||||
|
@app.route('/dashboard')
|
||||||
|
def dashboard():
|
||||||
|
if session.get('loggedIn', False) == False:
|
||||||
|
flash('You must be signed in to view the dashboard!', 'error')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
db = dbload()
|
||||||
|
udb = udbload()
|
||||||
|
username = session.get('username')
|
||||||
|
userfiles = []
|
||||||
|
quota_usage_gb = 0
|
||||||
|
if not username:
|
||||||
|
flash('An error occurred. Please sign in again.', 'error')
|
||||||
|
session['loggedIn'] = False
|
||||||
|
session.pop('username', None)
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
for file in db['files']:
|
||||||
|
if db['files'][file].get('owner') == username:
|
||||||
|
db['files'][file]['file'] = file
|
||||||
|
db['files'][file]['code'] = file.split('_')[-1]
|
||||||
|
userfiles.append(db['files'][file])
|
||||||
|
for userfile in userfiles:
|
||||||
|
usf_mb = userfile['size_megabytes']
|
||||||
|
if usf_mb:
|
||||||
|
quota_usage_gb += usf_mb / 1024
|
||||||
|
quota_usage_gb = round(quota_usage_gb, 1)
|
||||||
|
return render_template('dashboard.html', files=userfiles, username=username, quota_gb=udb[[user['username'] for user in udb].index(username)]['quota_gb'], quota_usage=quota_usage_gb)
|
||||||
|
@app.route('/logout')
|
||||||
|
def logout():
|
||||||
|
session['loggedIn'] = False
|
||||||
|
session.pop('username', None)
|
||||||
|
flash('Successfully signed out!', 'info')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
|
||||||
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
udb = udbload()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('login.html')
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form.get('username')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
for user in udb:
|
||||||
|
if user['username'] == username and user['password'] == password:
|
||||||
|
session['loggedIn'] = True
|
||||||
|
session['username'] = username
|
||||||
|
flash('Successfully signed in!', 'info')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
flash('Invalid username or password!', 'error')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/register', methods=['GET', 'POST'])
|
||||||
|
def register():
|
||||||
|
udb = udbload()
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('register.html')
|
||||||
|
if request.method == 'POST':
|
||||||
|
username = request.form.get('username')
|
||||||
|
password = request.form.get('password')
|
||||||
|
|
||||||
|
for user in udb:
|
||||||
|
if user['username'] == username:
|
||||||
|
flash('Username already taken!', 'error')
|
||||||
|
return redirect(url_for('register'))
|
||||||
|
|
||||||
|
udb.append({
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
'quota_gb': 3
|
||||||
|
})
|
||||||
|
udbsave(udb)
|
||||||
|
flash('Successfully registered! You can now sign in.', 'info')
|
||||||
|
return redirect(url_for('login'))
|
||||||
|
|
||||||
|
@app.route('/upload')
|
||||||
|
def upload():
|
||||||
|
loggedIn = session.get('loggedIn', False)
|
||||||
|
username = session.get('username', '')
|
||||||
|
quota_gb = None
|
||||||
|
quota_usage_gb = 0.0
|
||||||
|
try:
|
||||||
|
if loggedIn and username:
|
||||||
|
db = dbload()
|
||||||
|
udb = udbload()
|
||||||
|
# find user record in udb
|
||||||
|
user_rec = None
|
||||||
|
for u in udb:
|
||||||
|
if u.get('username') == username:
|
||||||
|
user_rec = u
|
||||||
|
break
|
||||||
|
if user_rec:
|
||||||
|
quota_gb = user_rec.get('quota_gb', 0)
|
||||||
|
else:
|
||||||
|
quota_gb = 0
|
||||||
|
userfiles = []
|
||||||
|
for file in db.get('files', {}):
|
||||||
|
if db['files'][file].get('owner') == username:
|
||||||
|
userfiles.append(db['files'][file])
|
||||||
|
for userfile in userfiles:
|
||||||
|
usf_mb = userfile.get('size_megabytes', 0)
|
||||||
|
if usf_mb:
|
||||||
|
quota_usage_gb += usf_mb / 1024
|
||||||
|
quota_usage_gb = round(quota_usage_gb, 1)
|
||||||
|
else:
|
||||||
|
# not logged in: treat as guest with 1GB manageable quota
|
||||||
|
quota_gb = 5.0
|
||||||
|
quota_usage_gb = 0.0
|
||||||
|
except Exception:
|
||||||
|
quota_gb = quota_gb or 0
|
||||||
|
quota_usage_gb = quota_usage_gb or 0.0
|
||||||
|
|
||||||
|
return render_template('upload.html', loggedIn=loggedIn, username=username, quota_gb=quota_gb, quota_usage=quota_usage_gb)
|
||||||
|
|
||||||
|
@app.route('/sendfile', methods=['POST'])
|
||||||
|
def sendfile():
|
||||||
|
db = dbload()
|
||||||
|
file = request.files['file']
|
||||||
|
reusable = request.form.get('reusable')
|
||||||
|
loggedIn = session.get('loggedIn', False)
|
||||||
|
username = session.get('username', '')
|
||||||
|
|
||||||
|
|
||||||
|
if file:
|
||||||
|
try:
|
||||||
|
extension = os.path.splitext(file.filename)[1].lower()
|
||||||
|
|
||||||
|
fileid = str(random.randint(100000, 99999999))
|
||||||
|
dest_name = secure_filename(file.filename) + f'_{fileid}'
|
||||||
|
dest_path = os.path.join(app.config['UPLOAD_DIRECTORY'], dest_name)
|
||||||
|
|
||||||
|
# save file first, then inspect size on disk
|
||||||
|
file.save(dest_path)
|
||||||
|
print(dest_name)
|
||||||
|
|
||||||
|
# get file size in bytes and convert to MB/GB
|
||||||
|
try:
|
||||||
|
size_bytes = os.path.getsize(dest_path)
|
||||||
|
except Exception:
|
||||||
|
size_bytes = 0
|
||||||
|
size_megabytes = round(size_bytes / (1024 * 1024), 1)
|
||||||
|
file_gb = size_megabytes / 1024
|
||||||
|
|
||||||
|
# determine user's remaining quota
|
||||||
|
remaining_gb = None
|
||||||
|
if loggedIn and username:
|
||||||
|
try:
|
||||||
|
udb = udbload()
|
||||||
|
# find user record
|
||||||
|
user_rec = None
|
||||||
|
for u in udb:
|
||||||
|
if u.get('username') == username:
|
||||||
|
user_rec = u
|
||||||
|
break
|
||||||
|
user_quota_gb = user_rec.get('quota_gb', 0) if user_rec else 0
|
||||||
|
# compute current usage
|
||||||
|
usage_gb = 0.0
|
||||||
|
for fkey, fval in db.get('files', {}).items():
|
||||||
|
if fval.get('owner') == username:
|
||||||
|
usf_mb = fval.get('size_megabytes', 0)
|
||||||
|
if usf_mb:
|
||||||
|
usage_gb += usf_mb / 1024
|
||||||
|
usage_gb = round(usage_gb, 1)
|
||||||
|
remaining_gb = max(0.0, user_quota_gb - usage_gb)
|
||||||
|
except Exception:
|
||||||
|
remaining_gb = 0.0
|
||||||
|
else:
|
||||||
|
# guests: they don't get owner set; treat remaining as 0 so owner won't be set
|
||||||
|
remaining_gb = 0.0
|
||||||
|
|
||||||
|
# build DB entry; only set owner if user is logged in AND file fits remaining quota
|
||||||
|
entry = {"reusable": True if reusable else False, "size_megabytes": size_megabytes}
|
||||||
|
if loggedIn and username and file_gb <= remaining_gb:
|
||||||
|
entry["owner"] = username
|
||||||
|
|
||||||
|
db["files"][dest_name] = entry
|
||||||
|
|
||||||
|
if reusable:
|
||||||
|
flash('Download code: ' + fileid, 'info')
|
||||||
|
else:
|
||||||
|
flash('1-Time Download code: ' + fileid, 'info')
|
||||||
|
|
||||||
|
dbsave(db)
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
|
||||||
|
except RequestEntityTooLarge:
|
||||||
|
return 'File is larger than the size limit.'
|
||||||
|
|
||||||
|
@app.route('/delete/<code>', methods=['GET', 'POST'])
|
||||||
|
def delete(code):
|
||||||
|
db = dbload()
|
||||||
|
for file in db['files']:
|
||||||
|
if file.split('_')[-1] == code:
|
||||||
|
if db['files'][file].get('owner') != session.get('username'):
|
||||||
|
flash('You do not own this file and cannot delete it.', 'error')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
try:
|
||||||
|
os.remove('uploads/' + file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('couldnt delete from os bc file not found.')
|
||||||
|
try:
|
||||||
|
os.remove('uploads/' + file.split('_')[0])
|
||||||
|
except FileNotFoundError:
|
||||||
|
print('couldnt delete from os bc file not found. (2)')
|
||||||
|
db['files'].pop(file)
|
||||||
|
dbsave(db)
|
||||||
|
flash('File with code ' + code + ' has been deleted.', 'info')
|
||||||
|
return redirect(url_for('dashboard'))
|
||||||
|
|
||||||
|
@app.route('/download')
|
||||||
|
def download_without_code():
|
||||||
|
flash('No code provided!', 'error')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
|
||||||
|
@app.route('/download/<code>', methods=['GET', 'POST'])
|
||||||
|
def download(code):
|
||||||
|
db = dbload()
|
||||||
|
try:
|
||||||
|
found = False
|
||||||
|
filename = ''
|
||||||
|
files = os.listdir('uploads')
|
||||||
|
for file in files:
|
||||||
|
if file.split('_')[-1] == code:
|
||||||
|
found = True
|
||||||
|
filename = file
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print(filename)
|
||||||
|
# Search db for the entry with this code and remove it
|
||||||
|
for file_entry in list(db['files'].keys()):
|
||||||
|
if file_entry.split('_')[-1] == code:
|
||||||
|
print(f"Deleting: {file_entry}")
|
||||||
|
print
|
||||||
|
db['files'].pop(file_entry)
|
||||||
|
dbsave(db)
|
||||||
|
print(f"Deleted! DB now has {len(db['files'])} files")
|
||||||
|
break
|
||||||
|
flash('Invalid code! Check if you typed the correct code, and for one-time codes, make sure nobody else entered the code before you did.', 'error')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
else:
|
||||||
|
os.rename('uploads/' + filename, 'uploads/' + filename.replace(f'_{code}', ''))
|
||||||
|
def deleteFile():
|
||||||
|
db['files'].pop(filename)
|
||||||
|
dbsave(db)
|
||||||
|
def backRename():
|
||||||
|
time.sleep(1)
|
||||||
|
os.rename('uploads/' + filename.replace(f'_{code}', ''), 'uploads/' + filename)
|
||||||
|
if db['files'][filename]['reusable']:
|
||||||
|
backToNameFunc = threading.Thread(target=backRename)
|
||||||
|
backToNameFunc.start()
|
||||||
|
if db['files'][filename]['reusable'] == False:
|
||||||
|
deleteFunc = threading.Thread(target=deleteFile)
|
||||||
|
deleteFunc.start()
|
||||||
|
|
||||||
|
return send_from_directory(app.config['UPLOAD_DIRECTORY'], filename.replace(f'_{code}', ''), as_attachment=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in download: {e}")
|
||||||
|
print(f"Type: {type(e)}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
flash('Invalid code! Check if you typed the correct code, and for one-time codes, make sure nobody else entered the code before you did.', 'error')
|
||||||
|
return redirect(url_for('upload'))
|
||||||
|
|
||||||
|
app.run(debug=True, port=3133)
|
||||||
2
instructions.txt
Normal file
2
instructions.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
run 'screen' then hit enter 2 times
|
||||||
|
run 'sh run.sh'
|
||||||
2
run.sh
Normal file
2
run.sh
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
echo "Running app in debug mode WARNING - disable debug mode for production"
|
||||||
|
python3 app.py
|
||||||
119
static/script.js
Normal file
119
static/script.js
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
if (localStorage.getItem("loaded")) {
|
||||||
|
document.getElementById("mirrorLogo").className = "fa-solid fa-circle-dot fa-2xs";
|
||||||
|
document.getElementById("mirrorText").textContent = "drive.fybe.dev";
|
||||||
|
document.getElementById("mirrorLogo").style.color = "#47cc00";
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById("mirrorLogo").style.color = "#47cc00";
|
||||||
|
document.getElementById("mirrorText").textContent = "drive.fybe.dev";
|
||||||
|
setTimeout(() => {
|
||||||
|
document.getElementById("mirrorLogo").className = "fa-solid fa-circle-dot fa-2xs";
|
||||||
|
localStorage.setItem("loaded", true);
|
||||||
|
}, 3000);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const form = document.getElementById('uploadForm');
|
||||||
|
if (!form) return;
|
||||||
|
|
||||||
|
const fileInput = document.getElementById('fileUpload');
|
||||||
|
const progress = document.getElementById('uploadProgress');
|
||||||
|
const progressText = document.getElementById('uploadProgressText');
|
||||||
|
const uploadBtn = document.getElementById('uploadBtn');
|
||||||
|
|
||||||
|
form.addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = fileInput.files[0];
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
// include reusable checkbox if present
|
||||||
|
const reusable = form.querySelector('input[name="reusable"]');
|
||||||
|
if (reusable && reusable.checked) formData.append('reusable', 'on');
|
||||||
|
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('POST', form.getAttribute('action'));
|
||||||
|
|
||||||
|
xhr.upload.addEventListener('progress', function(ev) {
|
||||||
|
if (ev.lengthComputable) {
|
||||||
|
const percent = Math.round((ev.loaded / ev.total) * 100);
|
||||||
|
if (progress) {
|
||||||
|
progress.style.display = 'block';
|
||||||
|
progress.value = percent;
|
||||||
|
}
|
||||||
|
if (progressText) {
|
||||||
|
progressText.style.display = 'block';
|
||||||
|
progressText.textContent = percent + '%';
|
||||||
|
}
|
||||||
|
if (uploadBtn) {
|
||||||
|
uploadBtn.classList.add('yellowBtn');
|
||||||
|
uploadBtn.value = '· · ·';
|
||||||
|
uploadBtn.disabled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('load', function() {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 400) {
|
||||||
|
// Try to extract flash messages from the returned HTML and inject them
|
||||||
|
try {
|
||||||
|
const tmp = document.createElement('div');
|
||||||
|
tmp.innerHTML = xhr.responseText;
|
||||||
|
const newFlashes = tmp.querySelector('ul.flashes');
|
||||||
|
const oldFlashes = document.querySelector('ul.flashes');
|
||||||
|
if (newFlashes) {
|
||||||
|
if (oldFlashes) oldFlashes.replaceWith(newFlashes);
|
||||||
|
else document.body.insertBefore(newFlashes, document.body.firstChild);
|
||||||
|
} else {
|
||||||
|
// fallback to full reload if no flashes present
|
||||||
|
window.location.href = '/upload';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
window.location.href = '/upload';
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
if (uploadBtn) {
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
uploadBtn.classList.remove('yellowBtn');
|
||||||
|
uploadBtn.value = 'Upload';
|
||||||
|
}
|
||||||
|
if (progress) { progress.style.display = 'none'; progress.value = 0; }
|
||||||
|
if (progressText) { progressText.style.display = 'none'; progressText.textContent = '0%'; }
|
||||||
|
// clear file input and reusable checkbox after successful upload
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
if (reusable && reusable.checked) reusable.checked = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// error
|
||||||
|
if (progressText) progressText.textContent = 'Upload failed';
|
||||||
|
if (uploadBtn) {
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
uploadBtn.classList.remove('yellowBtn');
|
||||||
|
uploadBtn.value = 'Upload';
|
||||||
|
}
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
if (progress) { progress.style.display = 'none'; progress.value = 0; }
|
||||||
|
if (progressText) progressText.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.addEventListener('error', function() {
|
||||||
|
if (progressText) progressText.textContent = 'Upload error';
|
||||||
|
if (uploadBtn) {
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
uploadBtn.classList.remove('yellowBtn');
|
||||||
|
uploadBtn.value = 'Upload';
|
||||||
|
}
|
||||||
|
if (fileInput) fileInput.value = '';
|
||||||
|
if (progress) { progress.style.display = 'none'; progress.value = 0; }
|
||||||
|
if (progressText) progressText.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
xhr.send(formData);
|
||||||
|
});
|
||||||
|
});
|
||||||
336
static/styles.css
Normal file
336
static/styles.css
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
:root {
|
||||||
|
--black: #000000;
|
||||||
|
--almost-black: #080708;
|
||||||
|
--blue: #3772FF;
|
||||||
|
--blue-light: #3787ff;
|
||||||
|
--red: #DF2935;
|
||||||
|
--yellow: #FDCA40;
|
||||||
|
--light: #E6E8E6;
|
||||||
|
--white: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: var(--white);
|
||||||
|
background: var(--black);
|
||||||
|
background-image: linear-gradient(to bottom right, var(--almost-black), var(--black));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
body,html {
|
||||||
|
width: 100%; height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: 50%;
|
||||||
|
border: 1px solid rgb(40, 40, 40);
|
||||||
|
padding-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding: 15px 0 0 15px;
|
||||||
|
width: 50%;
|
||||||
|
color: var(--light);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1200px) {
|
||||||
|
.app {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.images-section {
|
||||||
|
padding-top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.images-section a {
|
||||||
|
padding-bottom: 60%;
|
||||||
|
width: calc(100% - 15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms */
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='file'], input[type='text'], input[type='password'] {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 10px 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--almost-black);
|
||||||
|
background: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='submit'],
|
||||||
|
button[type='button'],
|
||||||
|
.sign-in-button {
|
||||||
|
padding: 13px 20px;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--white);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(--blue);
|
||||||
|
background-image: linear-gradient(to bottom, var(--blue-light), var(--blue));
|
||||||
|
transition: box-shadow .2s ease;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='submit']:hover,
|
||||||
|
button[type='button']:hover,
|
||||||
|
.sign-in-button:hover {
|
||||||
|
box-shadow: inset 0 -25px 25px var(--blue-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flashes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
.flashes li {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
border: 1px solid gray;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
border: 1px solid rgb(168, 50, 50);
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
border: 1px solid rgb(30, 199, 199);
|
||||||
|
}
|
||||||
|
.warning, .warn {
|
||||||
|
border: 1px solid rgb(232, 148, 1);
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellowBtn {
|
||||||
|
padding: 13px 20px;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--white);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
background: var(yellow) !important;
|
||||||
|
background-image: linear-gradient(to bottom, var(--yellow), var(--yellow)) !important;
|
||||||
|
transition: box-shadow .2s ease;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.yellowBtn:hover {
|
||||||
|
box-shadow: inset 0 -25px 25px var(--yellow) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-container {
|
||||||
|
padding: 15px 15px 15px 15px;
|
||||||
|
width: 100%;
|
||||||
|
color: var(--light);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
/* Table Styling */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
background: rgba(20, 20, 20, 0.8);
|
||||||
|
border: 1px solid rgb(60, 60, 60);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
background: linear-gradient(to bottom, rgb(50, 50, 50), rgb(40, 40, 40));
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--white);
|
||||||
|
border-bottom: 2px solid rgb(80, 80, 80);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid rgb(40, 40, 40);
|
||||||
|
color: var(--light);
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr:hover {
|
||||||
|
background: rgba(55, 114, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
table a {
|
||||||
|
color: var(--blue);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
table a:hover {
|
||||||
|
color: var(--blue-light);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Styled select for forms */
|
||||||
|
.fancy-select,
|
||||||
|
select.fancy-select {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
padding: 10px 44px 10px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 22px;
|
||||||
|
color: var(--almost-black);
|
||||||
|
background-color: var(--white);
|
||||||
|
border: 1px solid rgba(0,0,0,0.12);
|
||||||
|
background-image: linear-gradient(to bottom, #ffffff, #f7f7f7), url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24'><path d='M7 10l5 5 5-5z' fill='%23666'/></svg>");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: right 12px center;
|
||||||
|
background-size: 18px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: box-shadow .12s ease, border-color .12s ease, transform .06s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fancy-select:focus,
|
||||||
|
select.fancy-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--blue-light);
|
||||||
|
box-shadow: 0 0 0 4px rgba(55,114,255,0.09);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* hide default arrow in IE */
|
||||||
|
select.fancy-select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* smaller devices: reduce font-size and padding */
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.fancy-select,
|
||||||
|
select.fancy-select {
|
||||||
|
font-size: 18px;
|
||||||
|
padding-right: 40px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quota donut styles */
|
||||||
|
.dashboard-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 18px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.dashboard-meta {
|
||||||
|
flex: 1 1 260px;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
.quota-block {
|
||||||
|
margin: 8px 0 18px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
.quota-donut {
|
||||||
|
position: relative;
|
||||||
|
width: 110px;
|
||||||
|
height: 110px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.quota-donut svg {
|
||||||
|
display: block;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.donut-ring {
|
||||||
|
stroke: rgba(255,255,255,0.06);
|
||||||
|
stroke-linecap: round;
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
shape-rendering: geometricPrecision;
|
||||||
|
}
|
||||||
|
|
||||||
|
.donut-fill {
|
||||||
|
stroke: var(--blue);
|
||||||
|
stroke-linecap: round;
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
shape-rendering: geometricPrecision;
|
||||||
|
transition: stroke-dashoffset 600ms cubic-bezier(.22,.9,.3,1), stroke 200ms ease;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
.quota-center {
|
||||||
|
position: absolute;
|
||||||
|
left: 0; top: 0; right: 0; bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
.quota-percent {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.quota-number {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--light);
|
||||||
|
}
|
||||||
|
.quota-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.quota-donut { width: 64px; height: 64px; }
|
||||||
|
.quota-number { font-size: 12px; }
|
||||||
|
.quota-percent { font-size: 14px; }
|
||||||
|
}
|
||||||
132
templates/dashboard.html
Normal file
132
templates/dashboard.html
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ADrive Share</title>
|
||||||
|
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="flashes">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<!-- category: message, error, info, warning -->
|
||||||
|
<li class="{{ category }}"><strong>{{ message }}</strong></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
<div class="dashboard-container">
|
||||||
|
<div class="dashboard-top">
|
||||||
|
<div class="dashboard-meta">
|
||||||
|
<h2>Dashboard of {{username}}</h2>
|
||||||
|
<p style="color: gray; font-size: 15px; text-align: left;">
|
||||||
|
You are managing {{ quota_usage }}GB out of {{ quota_gb }}GB.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quota donut visual -->
|
||||||
|
{% set r = 40 %}
|
||||||
|
{% set c = (2 * 3.141592653589793 * r) %}
|
||||||
|
{% if quota_gb and quota_gb > 0 %}
|
||||||
|
{% set used = quota_usage %}
|
||||||
|
{% set pct = (used / quota_gb) if quota_gb > 0 else 0 %}
|
||||||
|
{% else %}
|
||||||
|
{% set used = 0 %}
|
||||||
|
{% set pct = 0 %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if pct >= 0.9 %}
|
||||||
|
{% set stroke_color = '#DF2935' %}
|
||||||
|
{% elif pct >= 0.6 %}
|
||||||
|
{% set stroke_color = '#FDCA40' %}
|
||||||
|
{% else %}
|
||||||
|
{% set stroke_color = '#3772FF' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="quota-block">
|
||||||
|
<div class="quota-donut" role="img" aria-label="Quota usage">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
|
||||||
|
<g transform="translate(50,50)">
|
||||||
|
<circle class="donut-ring" r="{{ r }}" cx="0" cy="0" fill="transparent" stroke-width="10" stroke-linecap="round"></circle>
|
||||||
|
<circle class="donut-fill" r="{{ r }}" cx="0" cy="0" fill="transparent" stroke-width="10" stroke-linecap="round"
|
||||||
|
stroke-dasharray="{{ c }}" stroke-dashoffset="{{ (c * (1 - pct)) }}" stroke="{{ stroke_color }}"></circle>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<div class="quota-center">
|
||||||
|
<div class="quota-percent">{{ (pct * 100) | round }}%</div>
|
||||||
|
<div class="quota-number">{{ ('%.1f' % used) if (used is not none) else '0.0' }} / {{ ('%.1f' % quota_gb) if (quota_gb is not none) else '0.0' }} GB</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Filename</th>
|
||||||
|
<th>Code</th>
|
||||||
|
<th>Reusable</th>
|
||||||
|
<th>Download</th>
|
||||||
|
<th>Delete</th>
|
||||||
|
</tr>
|
||||||
|
{% for file in files %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ file.file.rsplit('_', 1)[0] }}</td>
|
||||||
|
<td>{{ file.code }}</td>
|
||||||
|
<td>{{ 'Yes' if file.reusable else 'No' }}</td>
|
||||||
|
<td>
|
||||||
|
{% if file.reusable %}
|
||||||
|
<a href="/download/{{ file.code }}">Download</a>
|
||||||
|
{% else %}
|
||||||
|
<a href="/download/{{ file.code }}" style="color: rgb(255, 162, 0);">Download and Delete</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if file.reusable %}
|
||||||
|
<a href="/delete/{{ file.code }}" style="color: red;">Delete</a>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Upload a New File</a>
|
||||||
|
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Sign Out</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
|
||||||
|
|
||||||
|
function downloadClick(){
|
||||||
|
const codeEl = document.getElementById("codeInput");
|
||||||
|
const code = codeEl.value;
|
||||||
|
// clear input when clicked
|
||||||
|
codeEl.value = '';
|
||||||
|
const url = `/download/` + code;
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
function uploadAnim(e) {
|
||||||
|
//if (document.getElementById("fileUpload").value == "") {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("redBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "! ! !";
|
||||||
|
// e.preventDefault();
|
||||||
|
//} else {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
//}
|
||||||
|
if (!document.getElementById("fileUpload").value == "") {
|
||||||
|
document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
74
templates/login.html
Normal file
74
templates/login.html
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ADrive Share</title>
|
||||||
|
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="flashes">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<!-- category: message, error, info, warning -->
|
||||||
|
<li class="{{ category }}"><strong>{{ message }}</strong></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<h1>Sign In</h1>
|
||||||
|
<p style="color: gray; font-size: 15px; text-align: left;">
|
||||||
|
Enter your credentials to access your dashboard and manage your files.
|
||||||
|
</p>
|
||||||
|
<form id="loginForm" action="/login" method="post">
|
||||||
|
Username: <input type="text" name="username" id="username" placeholder="📋" required aria-required>
|
||||||
|
Passphrase: <input type="password" name="password" id="password" placeholder="🗝️" aria-required>
|
||||||
|
<input type="submit" value="Sign In" id="uploadBtn">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a href="/register" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Sign Up</a>
|
||||||
|
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Guest</a>
|
||||||
|
|
||||||
|
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
|
||||||
|
|
||||||
|
function downloadClick(){
|
||||||
|
const codeEl = document.getElementById("codeInput");
|
||||||
|
const code = codeEl.value;
|
||||||
|
// clear input when clicked
|
||||||
|
codeEl.value = '';
|
||||||
|
const url = `/download/` + code;
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
function uploadAnim(e) {
|
||||||
|
//if (document.getElementById("fileUpload").value == "") {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("redBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "! ! !";
|
||||||
|
// e.preventDefault();
|
||||||
|
//} else {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
//}
|
||||||
|
if (!document.getElementById("fileUpload").value == "") {
|
||||||
|
document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
76
templates/register.html
Normal file
76
templates/register.html
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ADrive Share</title>
|
||||||
|
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="flashes">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<!-- category: message, error, info, warning -->
|
||||||
|
<li class="{{ category }}"><strong>{{ message }}</strong></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<h1>Sign Up </h1>
|
||||||
|
<p style="color: gray; font-size: 15px; text-align: left;">
|
||||||
|
With an account, you can manage up to 3GB in one dashboard. If you use up your limit, you can delete to free up space or upload files without the ability to manage them.
|
||||||
|
</p>
|
||||||
|
<form id="loginForm" action="/register" method="post">
|
||||||
|
Username: <input type="text" name="username" id="username" placeholder="📋" required aria-required>
|
||||||
|
Passphrase: <input type="password" name="password" id="password" placeholder="🗝️" aria-required>
|
||||||
|
Managable Storage Quota: <select name="quota_files" id="quota_files" class="fancy-select" disabled aria-disabled>
|
||||||
|
<option value="3GB">3GB</option>
|
||||||
|
</select>
|
||||||
|
<input type="submit" class="sign-in-button" style="background: rgb(32, 0, 139)" value="Sign Up" id="uploadBtn">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<a class="sign-in-button" href="/login" style="background: rgb(0, 95, 139); text-decoration: none;" id="signininstead">Sign In Instead</a>
|
||||||
|
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Guest</a>
|
||||||
|
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free.</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
|
||||||
|
|
||||||
|
function downloadClick(){
|
||||||
|
const codeEl = document.getElementById("codeInput");
|
||||||
|
const code = codeEl.value;
|
||||||
|
// clear input when clicked
|
||||||
|
codeEl.value = '';
|
||||||
|
const url = `/download/` + code;
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
function uploadAnim(e) {
|
||||||
|
//if (document.getElementById("fileUpload").value == "") {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("redBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "! ! !";
|
||||||
|
// e.preventDefault();
|
||||||
|
//} else {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
//}
|
||||||
|
if (!document.getElementById("fileUpload").value == "") {
|
||||||
|
document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
152
templates/upload.html
Normal file
152
templates/upload.html
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>ADrive Share</title>
|
||||||
|
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="app">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
<ul class="flashes">
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<!-- category: message, error, info, warning -->
|
||||||
|
<li class="{{ category }}"><strong>{{ message }}</strong></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<form id="uploadForm" action="/sendfile" method="post" enctype="multipart/form-data">
|
||||||
|
{% if loggedIn %}
|
||||||
|
<h4>You are currently logged in as {{ username }}.</h4>
|
||||||
|
{% else %}
|
||||||
|
<h4>You are currently not logged in.</h4>
|
||||||
|
{% endif %}
|
||||||
|
<span style="font-size: 20px;">
|
||||||
|
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
|
||||||
|
<span id="mirrorText">Finding Nearby Mirror</span> <i class="fa-solid fa-circle-dot fa-beat-fade fa-2xs" style="color: #ff9500;" id="mirrorLogo"></i>
|
||||||
|
</span>
|
||||||
|
<span style="font-size: 20px;">
|
||||||
|
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
|
||||||
|
Location Not Found <i class="fa-solid fa-circle-dot fa-2xs" style="color: #c10101;"></i>
|
||||||
|
</span>
|
||||||
|
<input type="file" name="file" id="fileUpload" required aria-required>
|
||||||
|
<input type="hidden" id="quotaGB" value="{{ quota_gb }}">
|
||||||
|
<input type="hidden" id="quotaUsedGB" value="{{ quota_usage }}">
|
||||||
|
<progress id="uploadProgress" value="0" max="100" style="display:none;width:100%;margin-top:8px"></progress>
|
||||||
|
<div id="uploadProgressText" style="display:none;font-size:13px;margin-top:4px">0%</div>
|
||||||
|
<div style="display: flex; flex-direction: row;">
|
||||||
|
<input type="checkbox" name="reusable" style="display: inline-block;">
|
||||||
|
<span>Reusable?</span>
|
||||||
|
</div>
|
||||||
|
<input type="submit" value="Upload" id="uploadBtn" onclick="uploadAnim()" style="transition-duration: 400ms;">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form>
|
||||||
|
<input type="text" id="codeInput">
|
||||||
|
<button type="button" value="Download" onclick="downloadClick()">Download</button>
|
||||||
|
</form>
|
||||||
|
{% if not loggedIn %}
|
||||||
|
<a href="/login" class="sign-in-button" style="text-decoration: none; background: rgb(0, 81, 139)">Sign In</a>
|
||||||
|
<a href="/register" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Register</a><br>
|
||||||
|
<p style="color: gray; font-size: 11px; text-align: center;">Signing in is not required. You can at any time register and sign in to manage your existing codes for free. You can manage up to 1GB of files.</p>
|
||||||
|
{% else %}
|
||||||
|
<a href="/dashboard" class="sign-in-button" style="text-decoration: none; background: blue">View Dashboard</a>
|
||||||
|
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(79, 92, 178)">Sign Out</a>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
|
||||||
|
|
||||||
|
function downloadClick(){
|
||||||
|
const codeEl = document.getElementById("codeInput");
|
||||||
|
const code = codeEl.value;
|
||||||
|
// clear input when clicked
|
||||||
|
codeEl.value = '';
|
||||||
|
const url = `/download/` + code;
|
||||||
|
location.href = url;
|
||||||
|
}
|
||||||
|
function uploadAnim(e) {
|
||||||
|
//if (document.getElementById("fileUpload").value == "") {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("redBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "! ! !";
|
||||||
|
// e.preventDefault();
|
||||||
|
//} else {
|
||||||
|
// document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
// document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
//}
|
||||||
|
if (!document.getElementById("fileUpload").value == "") {
|
||||||
|
document.getElementById("uploadBtn").classList.add("yellowBtn");
|
||||||
|
document.getElementById("uploadBtn").value = "· · ·";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Client-side quota check before uploading large files
|
||||||
|
(function(){
|
||||||
|
const fileInput = document.getElementById('fileUpload');
|
||||||
|
const uploadBtn = document.getElementById('uploadBtn');
|
||||||
|
const quotaGB = parseFloat(document.getElementById('quotaGB').value) || 0;
|
||||||
|
const usedGB = parseFloat(document.getElementById('quotaUsedGB').value) || 0;
|
||||||
|
const remainingGB = Math.max(0, quotaGB - usedGB);
|
||||||
|
|
||||||
|
function ensureFlashesContainer(){
|
||||||
|
let flashes = document.querySelector('.flashes');
|
||||||
|
if (!flashes){
|
||||||
|
flashes = document.createElement('ul');
|
||||||
|
flashes.className = 'flashes';
|
||||||
|
const app = document.querySelector('.app');
|
||||||
|
if (app) app.insertBefore(flashes, app.firstChild);
|
||||||
|
}
|
||||||
|
return flashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearClientFlash(){
|
||||||
|
const flashes = document.querySelectorAll('.flashes li.client-quota-warning');
|
||||||
|
flashes.forEach(n => n.remove());
|
||||||
|
}
|
||||||
|
|
||||||
|
function showClientFlash(message){
|
||||||
|
clearClientFlash();
|
||||||
|
const flashes = ensureFlashesContainer();
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'warning client-quota-warning';
|
||||||
|
const strong = document.createElement('strong');
|
||||||
|
strong.textContent = message;
|
||||||
|
li.appendChild(strong);
|
||||||
|
flashes.prepend(li);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileInput){
|
||||||
|
fileInput.addEventListener('change', function(e){
|
||||||
|
clearClientFlash();
|
||||||
|
uploadBtn.disabled = false;
|
||||||
|
const f = fileInput.files && fileInput.files[0];
|
||||||
|
if (!f) return;
|
||||||
|
const fileGB = f.size / (1024 * 1024 * 1024);
|
||||||
|
if (fileGB > remainingGB){
|
||||||
|
showClientFlash('You do not have sufficient quota to manage this file. If you choose to upload this file, you will not be able to manage it through your dashboard.');
|
||||||
|
// still allow upload; server will accept but will not set owner when quota insufficient
|
||||||
|
} else {
|
||||||
|
clearClientFlash();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
5
test.py
Normal file
5
test.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from utils import *
|
||||||
|
|
||||||
|
db = dbload()
|
||||||
|
db['files'].pop("I_Love_You_-_Boy_Next_Door_1.mp3_45475777")
|
||||||
|
dbsave(db)
|
||||||
21
utils.py
Normal file
21
utils.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from flask import redirect as fredirect
|
||||||
|
import json
|
||||||
|
|
||||||
|
def redirect(url):
|
||||||
|
return fredirect(url)
|
||||||
|
|
||||||
|
def dbload():
|
||||||
|
with open('db.json', 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def dbsave(obj):
|
||||||
|
with open('db.json', 'w') as f:
|
||||||
|
json.dump(obj, f)
|
||||||
|
|
||||||
|
def udbload():
|
||||||
|
with open('users.json', 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def udbsave(obj):
|
||||||
|
with open('users.json', 'w') as f:
|
||||||
|
json.dump(obj, f)
|
||||||
Reference in New Issue
Block a user