Merge pull request '2601/ui-imprv - UI Improvements, Code Cleanup' (#2) from 2601a/ui-imprv into main
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2026-01-09 13:33:56 +09:00
4 changed files with 125 additions and 46 deletions

36
app.py
View File

@@ -11,7 +11,6 @@ from utils import redirect, dbload, dbsave, udbload, udbsave
from werkzeug.exceptions import RequestEntityTooLarge from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import sys
import os import os
import random import random
import threading import threading
@@ -39,32 +38,51 @@ def index():
@app.route('/dashboard') @app.route('/dashboard')
def dashboard(): def dashboard():
# Load Databases
db = dbload()
udb = udbload()
# Check if user is a guest
if session.get('loggedIn', False) == False: if session.get('loggedIn', False) == False:
flash('You must be signed in to view the dashboard!', 'error') flash('You must be signed in to view the dashboard!', 'error')
return redirect(url_for('upload')) return redirect(url_for('upload'))
db = dbload()
udb = udbload() # Get user quota, usage, and files
username = session.get('username') username = session.get('username')
userfiles = [] userfiles = []
quota_usage_gb = 0 quota_usage_gb = 0
if not username: if not username:
flash('An error occurred. Please sign in again.', 'error') flash('An error occurred. Please sign in again.', 'error')
session['loggedIn'] = False session['loggedIn'] = False
session.pop('username', None) session.pop('username', None)
return redirect(url_for('upload')) return redirect(url_for('upload'))
for file in db['files']: for file in db['files']:
if db['files'][file].get('owner') == username: if db['files'][file].get('owner') == username:
db['files'][file]['file'] = file db['files'][file]['file'] = file
db['files'][file]['code'] = file.split('_')[-1] db['files'][file]['code'] = file.split('_')[-1]
userfiles.append(db['files'][file]) userfiles.append(db['files'][file])
for userfile in userfiles: for userfile in userfiles:
usf_mb = userfile['size_megabytes'] usf_mb = userfile['size_megabytes']
if usf_mb: if usf_mb:
quota_usage_gb += usf_mb / 1024 quota_usage_gb += usf_mb / 1024
quota_usage_gb = round(quota_usage_gb, 1) 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)
# Render template with variables
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') @app.route('/logout')
def logout(): def logout():
# Sign out user and redirect to upload page
session['loggedIn'] = False session['loggedIn'] = False
session.pop('username', None) session.pop('username', None)
flash('Successfully signed out!', 'info') flash('Successfully signed out!', 'info')
@@ -72,43 +90,53 @@ def logout():
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
def login(): def login():
# Load User Database (UDB)
udb = udbload() udb = udbload()
if request.method == 'GET': if request.method == 'GET':
return render_template('login.html') return render_template('login.html')
if request.method == 'POST': if request.method == 'POST':
# Get data from form
username = request.form.get('username') username = request.form.get('username')
password = request.form.get('password') password = request.form.get('password')
# Check if user exists and password matches, log them in
for user in udb: for user in udb:
if user['username'] == username and user['password'] == password: if user['username'] == username and user['password'] == password:
session['loggedIn'] = True session['loggedIn'] = True
session['username'] = username session['username'] = username
flash('Successfully signed in!', 'info') flash('Successfully signed in!', 'info')
return redirect(url_for('upload')) return redirect(url_for('upload'))
# If still here, login failed
flash('Invalid username or password!', 'error') flash('Invalid username or password!', 'error')
return redirect(url_for('login')) return redirect(url_for('login'))
@app.route('/register', methods=['GET', 'POST']) @app.route('/register', methods=['GET', 'POST'])
def register(): def register():
# Load User Database (UDB)
udb = udbload() udb = udbload()
if request.method == 'GET': if request.method == 'GET':
return render_template('register.html') return render_template('register.html')
if request.method == 'POST': if request.method == 'POST':
# Get data from form
username = request.form.get('username') username = request.form.get('username')
password = request.form.get('password') password = request.form.get('password')
# Check if username is already taken
for user in udb: for user in udb:
if user['username'] == username: if user['username'] == username:
flash('Username already taken!', 'error') flash('Username already taken!', 'error')
return redirect(url_for('register')) return redirect(url_for('register'))
# Create new user with default quota of 3GB
udb.append({ udb.append({
'username': username, 'username': username,
'password': password, 'password': password,
'quota_gb': 3 'quota_gb': 3
}) })
udbsave(udb) udbsave(udb)
flash('Successfully registered! You can now sign in.', 'info') flash('Successfully registered! You can now sign in.', 'info')
return redirect(url_for('login')) return redirect(url_for('login'))

View File

@@ -19,6 +19,7 @@ document.addEventListener('DOMContentLoaded', function() {
const fileInput = document.getElementById('fileUpload'); const fileInput = document.getElementById('fileUpload');
const progress = document.getElementById('uploadProgress'); const progress = document.getElementById('uploadProgress');
const progressContainer = document.getElementById('uploadProgressContainer');
const progressText = document.getElementById('uploadProgressText'); const progressText = document.getElementById('uploadProgressText');
const uploadBtn = document.getElementById('uploadBtn'); const uploadBtn = document.getElementById('uploadBtn');
@@ -42,8 +43,10 @@ document.addEventListener('DOMContentLoaded', function() {
if (ev.lengthComputable) { if (ev.lengthComputable) {
const percent = Math.round((ev.loaded / ev.total) * 100); const percent = Math.round((ev.loaded / ev.total) * 100);
if (progress) { if (progress) {
progress.style.display = 'block'; if (progressContainer) progressContainer.style.display = 'block';
progress.value = percent; progress.style.width = percent + '%';
progress.setAttribute('aria-valuenow', percent);
progress.textContent = percent + '%';
} }
if (progressText) { if (progressText) {
progressText.style.display = 'block'; progressText.style.display = 'block';
@@ -82,7 +85,12 @@ document.addEventListener('DOMContentLoaded', function() {
uploadBtn.classList.remove('yellowBtn'); uploadBtn.classList.remove('yellowBtn');
uploadBtn.value = 'Upload'; uploadBtn.value = 'Upload';
} }
if (progress) { progress.style.display = 'none'; progress.value = 0; } if (progress) {
if (progressContainer) progressContainer.style.display = 'none';
progress.style.width = '0%';
progress.setAttribute('aria-valuenow', 0);
progress.textContent = '0%';
}
if (progressText) { progressText.style.display = 'none'; progressText.textContent = '0%'; } if (progressText) { progressText.style.display = 'none'; progressText.textContent = '0%'; }
// clear file input and reusable checkbox after successful upload // clear file input and reusable checkbox after successful upload
if (fileInput) fileInput.value = ''; if (fileInput) fileInput.value = '';
@@ -110,7 +118,12 @@ document.addEventListener('DOMContentLoaded', function() {
uploadBtn.value = 'Upload'; uploadBtn.value = 'Upload';
} }
if (fileInput) fileInput.value = ''; if (fileInput) fileInput.value = '';
if (progress) { progress.style.display = 'none'; progress.value = 0; } if (progress) {
if (progressContainer) progressContainer.style.display = 'none';
progress.style.width = '0%';
progress.setAttribute('aria-valuenow', 0);
progress.textContent = '0%';
}
if (progressText) progressText.style.display = 'none'; if (progressText) progressText.style.display = 'none';
}); });

View File

@@ -35,14 +35,15 @@ body,html {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
width: 50%; width: 80%;
border: 1px solid rgb(40, 40, 40); border: 1px solid rgb(40, 40, 40);
padding-bottom: 40px; padding-bottom: 40px;
padding: 50px;
} }
.form-section { .form-section {
padding: 15px 0 0 15px; padding: 15px 0 0 15px;
width: 50%; width: 100%;
color: var(--light); color: var(--light);
flex-shrink: 0; flex-shrink: 0;
} }
@@ -116,6 +117,8 @@ button[type='button']:hover,
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
padding: 10px;
} }
.flashes li { .flashes li {
list-style-type: none; list-style-type: none;

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -7,7 +8,9 @@
<title>ADrive Share</title> <title>ADrive Share</title>
<script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script> <script src="https://kit.fontawesome.com/972393379b.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head> </head>
<body> <body>
<div class="app"> <div class="app">
{% with messages = get_flashed_messages(with_categories=true) %} {% with messages = get_flashed_messages(with_categories=true) %}
@@ -15,7 +18,19 @@
<ul class="flashes"> <ul class="flashes">
{% for category, message in messages %} {% for category, message in messages %}
<!-- category: message, error, info, warning --> <!-- category: message, error, info, warning -->
<li class="{{ category }}"><strong>{{ message }}</strong></li> {% if category == 'message' %}
{% set category = 'primary' %}
{% endif %}
{% if category == 'error' %}
{% set category = 'danger' %}
{% endif %}
{% if category == 'info' %}
{% set category = 'info' %}
{% endif %}
{% if category == 'warning' %}
{% set category = 'warning' %}
{% endif %}
<li class="alert alert-{{ category }}">{{ message }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
{% endif %} {% endif %}
@@ -24,41 +39,56 @@
<div class="form-section"> <div class="form-section">
<form id="uploadForm" action="/sendfile" method="post" enctype="multipart/form-data"> <form id="uploadForm" action="/sendfile" method="post" enctype="multipart/form-data">
{% if loggedIn %} {% if loggedIn %}
<h4>You are currently logged in as {{ username }}.</h4> <h1>ADrive File Sharing <span class="badge text-bg-primary">{{ username }}</span></h1>
{% else %} {% else %}
<h4>You are currently not logged in.</h4> <h1>ADrive File Sharing <span class="badge text-bg-primary">Guest</span></h1>
{% endif %} {% endif %}
<span style="font-size: 20px;"> <span style="font-size: 20px;">
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i> <i class="fa-solid fa-server fa-xs"
<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> 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>
<span style="font-size: 20px;"> <span style="font-size: 20px;">
<i class="fa-solid fa-server fa-xs" style="line-height: 25px; font-size: 15px; display: inline-block;"></i> <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> Location Not Found <i class="fa-solid fa-circle-dot fa-2xs" style="color: #c10101;"></i>
</span> </span>
<input type="file" name="file" id="fileUpload" required aria-required> <input type="file" name="file" id="fileUpload" class="form-control" required aria-required>
<input type="hidden" id="quotaGB" value="{{ quota_gb }}"> <input type="hidden" id="quotaGB" value="{{ quota_gb }}">
<input type="hidden" id="quotaUsedGB" value="{{ quota_usage }}"> <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 class="progress" id="uploadProgressContainer" style="display:none;width:100%;margin-top:8px">
<div id="uploadProgressText" style="display:none;font-size:13px;margin-top:4px">0%</div> <div id="uploadProgress" class="progress-bar bg-success" role="progressbar" style="width:0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div>
<div style="display: flex; flex-direction: row;">
<input type="checkbox" name="reusable" style="display: inline-block;">
<span>Reusable?</span>
</div> </div>
<input type="submit" value="Upload" id="uploadBtn" onclick="uploadAnim()" style="transition-duration: 400ms;"> <div id="uploadProgressText" style="display:none;font-size:13px;margin-top:4px">0%</div>
<div class="input-group mb-3">
<div class="input-group-text">
<input class="form-check-input mt-0" type="checkbox" value="" name="reusable"
aria-label="Checkbox for following text input" style="border: 1px solid rgb(106, 106, 106);">
<label for="reusable" style="margin-left: 6px; margin-bottom: 0;">Reusable
Link</label>
</div>
</div>
<input type="submit" class="" value="Upload" id="uploadBtn" onclick="uploadAnim()"
style="transition-duration: 400ms;">
</form> </form>
<form> <form>
<input type="text" id="codeInput"> <input type="text" id="codeInput">
<button type="button" value="Download" onclick="downloadClick()">Download</button> <button type="button" value="Download" class="btn btn-primary" onclick="downloadClick()">Download</button>
</form> </form>
{% if not loggedIn %} {% if not loggedIn %}
<a href="/login" class="sign-in-button" style="text-decoration: none; background: rgb(0, 81, 139)">Sign In</a> <a href="/login" class="sign-in-button" style="text-decoration: none; background: rgb(0, 81, 139)">Sign
<a href="/register" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Register</a><br> In</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. You can manage up to 1GB of files.</p> <a href="/register" class="sign-in-button"
style="text-decoration: none; background: rgb(32, 0, 139)">Register</a><br><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 %} {% else %}
<a href="/dashboard" class="sign-in-button" style="text-decoration: none; background: blue">View Dashboard</a> <a href="/dashboard" class="sign-in-button" style="text-decoration: none; background: blue">View
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(79, 92, 178)">Sign Out</a> Dashboard</a>
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(79, 92, 178)">Sign
Out</a>
{% endif %} {% endif %}
</div> </div>
@@ -69,7 +99,7 @@
<script> <script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim); //document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){ function downloadClick() {
const codeEl = document.getElementById("codeInput"); const codeEl = document.getElementById("codeInput");
const code = codeEl.value; const code = codeEl.value;
// clear input when clicked // clear input when clicked
@@ -95,16 +125,16 @@
<script> <script>
// Client-side quota check before uploading large files // Client-side quota check before uploading large files
(function(){ (function () {
const fileInput = document.getElementById('fileUpload'); const fileInput = document.getElementById('fileUpload');
const uploadBtn = document.getElementById('uploadBtn'); const uploadBtn = document.getElementById('uploadBtn');
const quotaGB = parseFloat(document.getElementById('quotaGB').value) || 0; const quotaGB = parseFloat(document.getElementById('quotaGB').value) || 0;
const usedGB = parseFloat(document.getElementById('quotaUsedGB').value) || 0; const usedGB = parseFloat(document.getElementById('quotaUsedGB').value) || 0;
const remainingGB = Math.max(0, quotaGB - usedGB); const remainingGB = Math.max(0, quotaGB - usedGB);
function ensureFlashesContainer(){ function ensureFlashesContainer() {
let flashes = document.querySelector('.flashes'); let flashes = document.querySelector('.flashes');
if (!flashes){ if (!flashes) {
flashes = document.createElement('ul'); flashes = document.createElement('ul');
flashes.className = 'flashes'; flashes.className = 'flashes';
const app = document.querySelector('.app'); const app = document.querySelector('.app');
@@ -113,12 +143,12 @@
return flashes; return flashes;
} }
function clearClientFlash(){ function clearClientFlash() {
const flashes = document.querySelectorAll('.flashes li.client-quota-warning'); const flashes = document.querySelectorAll('.flashes li.client-quota-warning');
flashes.forEach(n => n.remove()); flashes.forEach(n => n.remove());
} }
function showClientFlash(message){ function showClientFlash(message) {
clearClientFlash(); clearClientFlash();
const flashes = ensureFlashesContainer(); const flashes = ensureFlashesContainer();
const li = document.createElement('li'); const li = document.createElement('li');
@@ -129,14 +159,14 @@
flashes.prepend(li); flashes.prepend(li);
} }
if (fileInput){ if (fileInput) {
fileInput.addEventListener('change', function(e){ fileInput.addEventListener('change', function (e) {
clearClientFlash(); clearClientFlash();
uploadBtn.disabled = false; uploadBtn.disabled = false;
const f = fileInput.files && fileInput.files[0]; const f = fileInput.files && fileInput.files[0];
if (!f) return; if (!f) return;
const fileGB = f.size / (1024 * 1024 * 1024); const fileGB = f.size / (1024 * 1024 * 1024);
if (fileGB > remainingGB){ 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.'); 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 // still allow upload; server will accept but will not set owner when quota insufficient
} else { } else {
@@ -148,5 +178,10 @@
</script> </script>
<script src="{{ url_for('static', filename='script.js') }}"></script> <script src="{{ url_for('static', filename='script.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
crossorigin="anonymous"></script>
</body> </body>
</html> </html>