Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 139 additions & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from lightdb import LightDB
from tools.geo_loc import geo_loc_bp
from tools.auth import auth_bp
from tools.db_auth import is_admin_user

from werkzeug.exceptions import RequestEntityTooLarge
from werkzeug.utils import secure_filename
Expand Down Expand Up @@ -78,7 +79,8 @@ def dashboard():
files=userfiles,
username=username,
quota_gb=udb[[user['username'] for user in udb].index(username)]['quota_gb'],
quota_usage=quota_usage_gb
quota_usage=quota_usage_gb,
is_admin=is_admin_user(username)
)

@app.route('/upload')
Expand Down Expand Up @@ -307,5 +309,141 @@ def backRename():
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.route('/admin')
def admin():
if not session.get('loggedIn', False):
flash('You must be signed in to access the admin panel!', 'error')
return redirect(url_for('upload'))

username = session.get('username')
if not is_admin_user(username):
flash('Access denied. Admin privileges required.', 'error')
return redirect(url_for('upload'))

db = l_db['files']
udb = l_db['users']

# Calculate per-user storage usage
user_usage = {}
user_file_count = {}
total_storage_mb = 0.0

for file_key in db:
owner = db[file_key].get('owner')
size_mb = db[file_key].get('size_megabytes', 0) or 0
total_storage_mb += size_mb
if owner:
user_usage[owner] = user_usage.get(owner, 0) + size_mb
user_file_count[owner] = user_file_count.get(owner, 0) + 1

total_storage_gb = round(total_storage_mb / 1024, 2)
total_files = len(db)

users_data = []
for user in udb:
uname = user.get('username', '')
usage_mb = user_usage.get(uname, 0)
usage_gb = round(usage_mb / 1024, 2)
users_data.append({
'username': uname,
'quota_gb': user.get('quota_gb', 0),
'usage_gb': usage_gb,
'file_count': user_file_count.get(uname, 0),
'is_admin': user.get('is_admin', False)
})

return render_template(
'admin.html',
username=username,
users=users_data,
total_storage_gb=total_storage_gb,
total_files=total_files,
total_users=len(users_data)
)


@app.route('/admin/update_quota', methods=['POST'])
def admin_update_quota():
if not session.get('loggedIn', False) or not is_admin_user(session.get('username')):
flash('Access denied.', 'error')
return redirect(url_for('upload'))

target = request.form.get('username')
try:
new_quota = float(request.form.get('quota_gb', 0))
except (ValueError, TypeError):
flash('Invalid quota value.', 'error')
return redirect(url_for('admin'))

udb = l_db['users']
users = list(udb)
for i, user in enumerate(users):
if user.get('username') == target:
users[i]['quota_gb'] = new_quota
l_db['users'] = users
flash(f"Quota for {target} updated to {new_quota}GB.", 'info')
return redirect(url_for('admin'))

flash('User not found.', 'error')
return redirect(url_for('admin'))


@app.route('/admin/delete_user', methods=['POST'])
def admin_delete_user():
if not session.get('loggedIn', False) or not is_admin_user(session.get('username')):
flash('Access denied.', 'error')
return redirect(url_for('upload'))

target = request.form.get('username')
if target == session.get('username'):
flash('You cannot delete your own account.', 'error')
return redirect(url_for('admin'))

udb = l_db['users']
users = list(udb)
new_users = [u for u in users if u.get('username') != target]
if len(new_users) == len(users):
flash('User not found.', 'error')
return redirect(url_for('admin'))

l_db['users'] = new_users
# Remove files owned by the deleted user
db = l_db['files']
files_to_delete = [fkey for fkey in db if db[fkey].get('owner') == target]
for fkey in files_to_delete:
try:
os.remove(os.path.join(app.config['UPLOAD_DIRECTORY'], fkey))
except FileNotFoundError:
pass
db.pop(fkey)
flash(f"User {target} has been deleted.", 'info')
return redirect(url_for('admin'))


@app.route('/admin/toggle_admin', methods=['POST'])
def admin_toggle_admin():
if not session.get('loggedIn', False) or not is_admin_user(session.get('username')):
flash('Access denied.', 'error')
return redirect(url_for('upload'))

target = request.form.get('username')
if target == session.get('username'):
flash('You cannot change your own admin status.', 'error')
return redirect(url_for('admin'))

udb = l_db['users']
users = list(udb)
for i, user in enumerate(users):
if user.get('username') == target:
users[i]['is_admin'] = not bool(user.get('is_admin', False))
l_db['users'] = users
status = 'granted' if users[i]['is_admin'] else 'revoked'
flash(f"Admin privileges {status} for {target}.", 'info')
return redirect(url_for('admin'))

flash('User not found.', 'error')
return redirect(url_for('admin'))


port = int(os.environ.get("ADRIVE_PORT", 3133))
app.run(debug=True, port=port, host='0.0.0.0')
188 changes: 188 additions & 0 deletions templates/admin.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
<!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 Admin Panel</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 data-bs-theme="dark">
<button class="theme-toggle" id="themeToggle" title="Toggle dark mode">
<i class="fas fa-moon"></i>
</button>
<div class="app" style="max-width: 1100px;">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<ul class="flashes">
{% for category, message in messages %}
{% if category == 'message' %}{% set category = 'primary' %}{% endif %}
{% if category == 'error' %}{% set category = 'danger' %}{% endif %}
<li class="alert alert-{{ category }}">{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}

<div class="dashboard-container">
<div class="dashboard-top">
<div class="dashboard-meta">
<h2><i class="fas fa-shield-alt"></i> Admin Panel</h2>
<p style="color: gray; font-size: 15px;">Signed in as <strong>{{ username }}</strong></p>
</div>
</div>

<!-- Storage Stats -->
<div class="row g-3 mb-4">
<div class="col-md-4">
<div class="card text-bg-primary">
<div class="card-body text-center">
<h5 class="card-title"><i class="fas fa-users"></i> Total Users</h5>
<p class="card-text fs-2 fw-bold">{{ total_users }}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-success">
<div class="card-body text-center">
<h5 class="card-title"><i class="fas fa-file"></i> Total Files</h5>
<p class="card-text fs-2 fw-bold">{{ total_files }}</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-bg-warning">
<div class="card-body text-center">
<h5 class="card-title"><i class="fas fa-hdd"></i> Total Storage Used</h5>
<p class="card-text fs-2 fw-bold">{{ total_storage_gb }} GB</p>
</div>
</div>
</div>
</div>

<!-- User Management Table -->
<h4 class="mb-3"><i class="fas fa-users-cog"></i> User Management</h4>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead>
<tr>
<th>Username</th>
<th>Admin</th>
<th>Files</th>
<th>Storage Used</th>
<th>Quota</th>
<th>Usage</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>
{{ user.username }}
{% if user.username == username %}
<span class="badge text-bg-secondary ms-1">you</span>
{% endif %}
</td>
<td>
{% if user.is_admin %}
<span class="badge text-bg-danger"><i class="fas fa-shield-alt"></i> Admin</span>
{% else %}
<span class="badge text-bg-secondary">User</span>
{% endif %}
</td>
<td>{{ user.file_count }}</td>
<td>{{ user.usage_gb }} GB</td>
<td>{{ user.quota_gb }} GB</td>
<td>
{% if user.quota_gb and user.quota_gb > 0 %}
{% set pct = ((user.usage_gb / user.quota_gb) * 100) | round | int %}
{% else %}
{% set pct = 0 %}
{% endif %}
<div class="progress" style="min-width: 80px;">
<div class="progress-bar
{% if pct >= 90 %}bg-danger
{% elif pct >= 60 %}bg-warning
{% else %}bg-primary{% endif %}"
role="progressbar"
style="width: {{ pct }}%"
aria-valuenow="{{ pct }}"
aria-valuemin="0"
aria-valuemax="100">{{ pct }}%</div>
</div>
</td>
<td>
<div class="d-flex gap-2 flex-wrap">
<!-- Update Quota -->
<form action="/admin/update_quota" method="post" class="d-flex gap-1 align-items-center">
<input type="hidden" name="username" value="{{ user.username }}">
<label class="visually-hidden" for="quota_{{ user.username }}">Quota (GB)</label>
<input type="number" name="quota_gb" id="quota_{{ user.username }}"
value="{{ user.quota_gb }}"
min="0" step="0.5"
class="form-control form-control-sm"
style="width: 80px;"
title="Quota in GB"
aria-label="Quota in GB">
<button type="submit" class="btn btn-sm btn-outline-primary" title="Save quota">
<i class="fas fa-save"></i>
</button>
</form>
{% if user.username != username %}
<!-- Toggle Admin -->
<form action="/admin/toggle_admin" method="post">
<input type="hidden" name="username" value="{{ user.username }}">
<button type="submit"
class="btn btn-sm {% if user.is_admin %}btn-outline-warning{% else %}btn-outline-success{% endif %}"
title="{% if user.is_admin %}Revoke Admin{% else %}Grant Admin{% endif %}">
<i class="fas fa-shield-alt"></i>
</button>
</form>
<!-- Delete User -->
<form action="/admin/delete_user" method="post"
onsubmit="return confirm('Delete user {{ user.username }}? This cannot be undone.')">
<input type="hidden" name="username" value="{{ user.username }}">
<button type="submit" class="btn btn-sm btn-outline-danger"
title="Delete User">
<i class="fas fa-trash"></i>
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr>
<td colspan="7" class="text-center text-muted">No users found.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

<div class="mt-3 d-flex gap-2">
<a href="/dashboard" class="sign-in-button" style="text-decoration: none; background: blue;">
<i class="fas fa-tachometer-alt"></i> Dashboard
</a>
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155);">
<i class="fas fa-upload"></i> Upload
</a>
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139);">
<i class="fas fa-sign-out-alt"></i> Sign Out
</a>
</div>
</div>
</div>

<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>
4 changes: 4 additions & 0 deletions templates/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ <h2>Dashboard of {{username}}</h2>
</table>
<a href="/upload" class="sign-in-button" style="text-decoration: none; background: rgb(1, 60, 155)">Upload a
New File</a>
{% if is_admin %}
<a href="/admin" class="sign-in-button" style="text-decoration: none; background: rgb(139, 0, 0)">
<i class="fas fa-shield-alt"></i> Admin Panel</a>
{% endif %}
<a href="/logout" class="sign-in-button" style="text-decoration: none; background: rgb(32, 0, 139)">Sign
Out</a>

Expand Down
Loading
Loading