Compare commits

...

22 Commits

Author SHA1 Message Date
69e7e66823 Merge pull request 'minor locating feature fixes' (#7) from lightmode into main
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
Reviewed-on: #7
2026-01-12 11:50:22 +09:00
39917b7351 minor locating feature fixes
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 11s
2026-01-12 11:49:56 +09:00
13d9cc9dc9 Merge pull request 'Change site to Light Mode for now' (#6) from lightmode into main
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 13s
Reviewed-on: #6
2026-01-12 11:49:15 +09:00
db5a88972b fix-process: 1
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-10 20:30:11 +09:00
98eb031c1c update requirements.txt
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-10 20:17:25 +09:00
68a044a679 show badge with visual colors
Some checks failed
Flask Run Test / Flask-Run-Test (push) Failing after 9s
2026-01-10 20:11:42 +09:00
ab84dea518 show location of user
Some checks failed
Flask Run Test / Flask-Run-Test (push) Failing after 9s
2026-01-10 20:07:23 +09:00
25617cb3c3 exclude instructions.txt from docker images
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-10 19:24:34 +09:00
10b143193b Make non-reusable files also deleteable without downloading at all times
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-10 19:22:31 +09:00
f9fca8c044 Make a better LIGHT-MODE table styling
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-10 19:21:29 +09:00
8298a4bae5 remove existing table design
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-10 19:18:53 +09:00
18a64279fd Apply white BG and black color
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-10 19:18:24 +09:00
4629e12f67 apply universal styling to flash alerts
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-10 19:08:12 +09:00
Andrew K
0bdb09b23c apply bootstrap; apply corrections in information text
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 11s
2026-01-10 19:06:33 +09:00
763d360be6 Merge pull request '2601b/docker' (#4) from 2601b/docker into main
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
Reviewed-on: #4
2026-01-09 15:53:59 +09:00
Andrew K
f3f9979a0e improve docker environment
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-09 15:08:32 +09:00
Andrew K
bd93caa375 make sure flask server is able to be accessed from host, outside container
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-09 15:02:13 +09:00
Andrew K
a48abc3b53 create docker files
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-09 15:01:56 +09:00
c27377cd60 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
2026-01-09 13:33:56 +09:00
Andrew K
cc12b95c98 use bootstrap style alerts, progress bars
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-09 13:30:36 +09:00
Andrew K
2be9e51805 clean up code with increased readability
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 10s
2026-01-08 23:23:45 +09:00
Andrew K
b19fce5b4a clean up unrequired import sys
All checks were successful
Flask Run Test / Flask-Run-Test (push) Successful in 9s
2026-01-08 23:20:55 +09:00
10 changed files with 372 additions and 148 deletions

15
.dockerignore Normal file
View File

@@ -0,0 +1,15 @@
.idea
__pycache__
.git
.dockerignore
Dockerfile
*.md
db.json
users.json
test.py
update_db.py
logs
*.log
.vscode
uploads/
instructions.txt

10
Dockerfile Normal file
View File

@@ -0,0 +1,10 @@
FROM python:3.9.5
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
COPY container/* .
RUN mkdir -p /app/uploads
EXPOSE 3133
CMD ["python", "app.py"]

96
app.py
View File

@@ -5,17 +5,20 @@ from flask import (
request,
url_for,
send_from_directory,
flash
flash,
jsonify
)
from utils import redirect, dbload, dbsave, udbload, udbsave
from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
from werkzeug.middleware.proxy_fix import ProxyFix
import sys
import os
import random
import threading
import time
import requests
import logging
import logging.handlers
@@ -25,6 +28,7 @@ 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))
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1)
# Redirect logs to the file 'logs'
handler = logging.handlers.RotatingFileHandler('logs', maxBytes=1024 * 1024)
@@ -33,38 +37,108 @@ logging.getLogger('werkzeug').addHandler(handler)
app.logger.setLevel(logging.WARNING)
app.logger.addHandler(handler)
@app.route('/get-location')
def get_location():
# Since you said /check-ip shows the correct IP, we use it directly
user_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
# Clean the IP if it's a list
if user_ip and ',' in user_ip:
user_ip = user_ip.split(',')[0].strip()
try:
# 1. We MUST send a User-Agent header or the API will block the request
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0'
}
# 2. Use ip-api.com (it's the most 'forgiving' for free testing)
response = requests.get(
f'http://ip-api.com/json/{user_ip}',
headers=headers,
timeout=5
)
data = response.json()
if data.get('status') == 'success':
return jsonify({
"status": "success",
"city": data.get('city'),
"country": data.get('country')
})
else:
# This helps you debug: what did the API actually say?
print(f"API Error Message: {data.get('message')}")
except Exception as e:
print(f"Python Request Failed: {e}")
return jsonify({"status": "fail", "city": "Unknown", "country": "Location"})
@app.route('/check-ip')
def check_ip():
return {
"remote_addr": request.remote_addr,
"x_forwarded_for": request.headers.get('X-Forwarded-For'),
"actual_ip_used": request.headers.get('X-Forwarded-For', request.remote_addr)
}
@app.route('/ping')
def ping():
return jsonify({"status": "ok"})
@app.route('/')
def index():
return redirect(url_for('upload'))
@app.route('/dashboard')
def dashboard():
# Load Databases
db = dbload()
udb = udbload()
# Check if user is a guest
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()
# Get user quota, usage, and files
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)
# 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')
def logout():
# Sign out user and redirect to upload page
session['loggedIn'] = False
session.pop('username', None)
flash('Successfully signed out!', 'info')
@@ -72,43 +146,53 @@ def logout():
@app.route('/login', methods=['GET', 'POST'])
def login():
# Load User Database (UDB)
udb = udbload()
if request.method == 'GET':
return render_template('login.html')
if request.method == 'POST':
# Get data from form
username = request.form.get('username')
password = request.form.get('password')
# Check if user exists and password matches, log them in
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'))
# If still here, login failed
flash('Invalid username or password!', 'error')
return redirect(url_for('login'))
@app.route('/register', methods=['GET', 'POST'])
def register():
# Load User Database (UDB)
udb = udbload()
if request.method == 'GET':
return render_template('register.html')
if request.method == 'POST':
# Get data from form
username = request.form.get('username')
password = request.form.get('password')
# Check if username is already taken
for user in udb:
if user['username'] == username:
flash('Username already taken!', 'error')
return redirect(url_for('register'))
# Create new user with default quota of 3GB
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'))
@@ -302,4 +386,4 @@ def download(code):
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)
app.run(debug=True, port=3133, host='0.0.0.0')

View File

@@ -1 +1,3 @@
Flask
requests
werkzeug

View File

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

View File

@@ -1,6 +1,6 @@
:root {
--black: #000000;
--almost-black: #080708;
--almostBlack: #888888;
--blue: #3772FF;
--blue-light: #3787ff;
--red: #DF2935;
@@ -19,9 +19,8 @@ body {
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));
color: var(--black);
background: var(--white);
display: flex;
flex-direction: column;
justify-content: center;
@@ -35,15 +34,18 @@ body,html {
display: flex;
flex-direction: column;
align-items: flex-start;
width: 50%;
width: 80%;
border: 1px solid rgb(40, 40, 40);
border-radius: 8px;
padding-bottom: 40px;
padding: 50px;
color: var(--black) !important;
}
.form-section {
padding: 15px 0 0 15px;
width: 50%;
color: var(--light);
width: 100%;
color: var(--black);
flex-shrink: 0;
}
@@ -116,6 +118,8 @@ button[type='button']:hover,
display: flex;
flex-direction: column;
gap: 15px;
padding: 10px;
}
.flashes li {
list-style-type: none;
@@ -164,57 +168,16 @@ button[type='button']:hover,
.dashboard-container {
padding: 15px 15px 15px 15px;
width: 100%;
color: var(--light);
color: var(--black);
flex-shrink: 0;
}
/* Table Styling */
/* Bootstrap-Friendly Table Design */
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;
font-size: 15px;
}
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;
table tr td {
font-weight: light;
}
/* Styled select for forms */
@@ -276,6 +239,7 @@ select.fancy-select::-ms-expand {
.quota-block {
margin: 8px 0 18px;
flex: 0 0 auto;
color: var(--almostBlack) !important;
}
.quota-donut {
position: relative;
@@ -290,7 +254,8 @@ select.fancy-select::-ms-expand {
height: 100%;
}
.donut-ring {
stroke: rgba(255,255,255,0.06);
stroke: rgba(9, 9, 9, 0.1);
/* fill: black; */
stroke-linecap: round;
vector-effect: non-scaling-stroke;
shape-rendering: geometricPrecision;
@@ -312,7 +277,7 @@ select.fancy-select::-ms-expand {
align-items: center;
justify-content: center;
pointer-events: none;
color: var(--white);
color: var(--almostBlack);
}
.quota-percent {
font-size: 18px;
@@ -322,7 +287,7 @@ select.fancy-select::-ms-expand {
.quota-number {
font-size: 12px;
font-weight: 700;
color: var(--light);
color: var(--almostBlack);
}
.quota-label {
font-size: 11px;

View File

@@ -6,6 +6,7 @@
<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 href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
@@ -15,7 +16,19 @@
<ul class="flashes">
{% for category, message in messages %}
<!-- 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 %}
</ul>
{% endif %}
@@ -64,7 +77,7 @@
</div>
</div>
</div>
<table>
<table class="table table-striped">
<tr>
<th>Filename</th>
<th>Code</th>
@@ -85,9 +98,7 @@
{% endif %}
</td>
<td>
{% if file.reusable %}
<a href="/delete/{{ file.code }}" style="color: red;">Delete</a>
{% endif %}
</td>
</tr>
{% endfor %}

View File

@@ -7,6 +7,7 @@
<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') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="app">
@@ -15,7 +16,19 @@
<ul class="flashes">
{% for category, message in messages %}
<!-- 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 %}
</ul>
{% endif %}
@@ -27,8 +40,8 @@
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>
Username: <input type="text" class="form-control" name="username" id="username" placeholder="📋" required aria-required>
Passphrase: <input type="password" class="form-control" name="password" id="password" placeholder="🗝️" aria-required>
<input type="submit" value="Sign In" id="uploadBtn">
</form>
@@ -70,5 +83,8 @@
</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>
</html>

View File

@@ -7,6 +7,7 @@
<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') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="app">
@@ -15,7 +16,19 @@
<ul class="flashes">
{% for category, message in messages %}
<!-- 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 %}
</ul>
{% endif %}
@@ -27,8 +40,8 @@
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>
Username: <input type="text" class="form-control" name="username" id="username" placeholder="📋" required aria-required>
Passphrase: <input type="password" class="form-control" 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>
@@ -72,5 +85,8 @@
</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>
</html>

View File

@@ -1,5 +1,6 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@@ -7,7 +8,9 @@
<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') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="app">
{% with messages = get_flashed_messages(with_categories=true) %}
@@ -15,7 +18,19 @@
<ul class="flashes">
{% for category, message in messages %}
<!-- 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 %}
</ul>
{% endif %}
@@ -24,41 +39,56 @@
<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>
<h1>ADrive File Sharing <span class="badge text-bg-primary">{{ username }}</span></h1>
{% else %}
<h4>You are currently not logged in.</h4>
<h1>ADrive File Sharing <span class="badge text-bg-danger">Guest</span></h1>
{% 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 style="font-size: 20px;">
<i class="fa-solid fa-wifi fa-xs"
style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
<span id="mirrorText">Ping: <span id="ping-value">CONNECTING</span>ms</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; padding-bottom: 7px;">
<i class="fa-solid fa-location fa-xs"
style="line-height: 25px; font-size: 15px; display: inline-block;"></i>
<span id="location-display" class="loading">Locating...</span> <i class="fa-solid fa-circle-dot fa-2xs" id="mirrorLogo" style="color: #c10101;"></i> <span class="badge text-bg-primary" id="ping-badge"><span id="ping-status"></span> <span id="ping-value">..</span>ms</span></span>
</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="file" name="file" id="fileUpload" class="form-control" 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 class="progress" id="uploadProgressContainer" style="display:none;width:100%;margin-top:8px">
<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>
<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="" id="reusableCheck" 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>
<input type="text" id="codeInput">
<button type="button" value="Download" onclick="downloadClick()">Download</button>
<input type="text" class="form-control" id="codeInput">
<button type="button" value="Download" class="btn btn-primary" 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>
<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><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 3GB 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>
<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>
@@ -69,7 +99,7 @@
<script>
//document.getElementById("uploadBtn").addEventListener("click", uploadAnim);
function downloadClick(){
function downloadClick() {
const codeEl = document.getElementById("codeInput");
const code = codeEl.value;
// clear input when clicked
@@ -95,16 +125,16 @@
<script>
// Client-side quota check before uploading large files
(function(){
(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(){
function ensureFlashesContainer() {
let flashes = document.querySelector('.flashes');
if (!flashes){
if (!flashes) {
flashes = document.createElement('ul');
flashes.className = 'flashes';
const app = document.querySelector('.app');
@@ -113,12 +143,12 @@
return flashes;
}
function clearClientFlash(){
function clearClientFlash() {
const flashes = document.querySelectorAll('.flashes li.client-quota-warning');
flashes.forEach(n => n.remove());
}
function showClientFlash(message){
function showClientFlash(message) {
clearClientFlash();
const flashes = ensureFlashesContainer();
const li = document.createElement('li');
@@ -129,14 +159,14 @@
flashes.prepend(li);
}
if (fileInput){
fileInput.addEventListener('change', function(e){
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){
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 {
@@ -147,6 +177,81 @@
})();
</script>
<script>
function measurePing() {
const start = Date.now();
fetch('/ping')
.then(() => {
const end = Date.now();
const latency = end - start;
document.getElementById('ping-value').innerText = latency;
if (latency > 200) {
document.getElementById('ping-status').innerText = "BAD";
document.getElementById('ping-badge').className = "badge text-bg-danger";
} else if (latency > 100) {
document.getElementById('ping-status').innerText = "GOOD";
document.getElementById('ping-badge').className = "badge text-bg-success";
} else if (latency <= 100) {
document.getElementById('ping-status').innerText = "BEST";
document.getElementById('ping-badge').className = "badge text-bg-primary";
}
})
.catch(err => {
document.getElementById('ping-value').innerText = "Error";
});
}
document.getElementById("fileUpload").disabled = true;
document.getElementById("uploadBtn").disabled = true;
document.getElementById("codeInput").disabled = true;
document.getElementById("reusableCheck").disabled = true;
// Measure ping immediately on load
setTimeout(() => {
document.getElementById("mirrorLogo").classList.remove("fa-beat-fade");
document.getElementById("mirrorLogo").style.color = "green";
measurePing();
document.getElementById("fileUpload").disabled = false;
document.getElementById("codeInput").disabled = false;
document.getElementById("uploadBtn").disabled = false;
document.getElementById("reusableCheck").disabled = false;
}, 1000);
setInterval(() => {
measurePing();
}, 1000); // Update every 30 seconds
</script>
<script>
async function loadLocation() {
try {
// This calls the API from the USER'S browser.
// The API will see the User's Real IP directly.
const response = await fetch('https://ipapi.co/json/');
const data = await response.json();
if (data.city && data.country_name) {
document.getElementById('location-display').innerText =
`${data.city}, ${data.country_name}`;
document.getElementById('location-display').classList.remove('loading');
} else {
throw new Error("Missing data");
}
} catch (err) {
console.error("Location Error:", err);
document.getElementById('location-display').innerText = "Unknown Location";
}
}
loadLocation();
</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>
</html>