re-Made server totally - version 1.0 now come.

This commit is contained in:
Anry Das
2025-10-19 09:22:23 +03:00
parent 74c658b2dd
commit 738fb9f179
17 changed files with 2308 additions and 724 deletions

266
templates/admin.html Normal file
View File

@@ -0,0 +1,266 @@
{% extends "base.html" %}
{% block title %}Admin Panel - Das File Storage{% endblock %}
{% block content %}
<div class="nav-tabs">
<a href="/dashboard" class="nav-tab">📂 My Files</a>
<a href="/settings" class="nav-tab">⚙️ Settings</a>
<a href="/admin" class="nav-tab active">👑 Admin Panel</a>
</div>
{% if message %}
<div class="alert alert-success">
{{ message }}
</div>
{% endif %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>User Management</h2>
<button onclick="openCreateUserModal()" class="btn btn-primary">
Create New User
</button>
</div>
<div style="background: #f9fafb; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
<h3 style="margin-bottom: 15px; color: #374151;">📊 Statistics</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px;">
<div style="background: white; padding: 15px; border-radius: 6px; border-left: 4px solid #667eea;">
<div style="color: #6b7280; font-size: 12px;">Total Users</div>
<div style="font-size: 24px; font-weight: bold; color: #374151;">{{ stats.total_users }}</div>
</div>
<div style="background: white; padding: 15px; border-radius: 6px; border-left: 4px solid #10b981;">
<div style="color: #6b7280; font-size: 12px;">Active Users</div>
<div style="font-size: 24px; font-weight: bold; color: #374151;">{{ stats.active_users }}</div>
</div>
<div style="background: white; padding: 15px; border-radius: 6px; border-left: 4px solid #ef4444;">
<div style="color: #6b7280; font-size: 12px;">Blocked Users</div>
<div style="font-size: 24px; font-weight: bold; color: #374151;">{{ stats.blocked_users }}</div>
</div>
<div style="background: white; padding: 15px; border-radius: 6px; border-left: 4px solid #f59e0b;">
<div style="color: #6b7280; font-size: 12px;">Total Files</div>
<div style="font-size: 24px; font-weight: bold; color: #374151;">{{ stats.total_files }}</div>
</div>
</div>
</div>
<table>
<thead>
<tr>
<th>Username</th>
<th>Role</th>
<th>Files</th>
<th>Created</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>
<strong>{{ user.username }}</strong>
</td>
<td>
{% if user.is_admin %}
<span class="admin-badge">Admin</span>
{% else %}
User
{% endif %}
</td>
<td class="file-size">{{ user.file_count }} files</td>
<td class="file-size">{{ user.created_at.strftime('%Y-%m-%d') }}</td>
<td>
{% if user.is_blocked %}
<span style="color: #ef4444; font-weight: bold;">🚫 Blocked</span>
{% else %}
<span style="color: #10b981; font-weight: bold;">✅ Active</span>
{% endif %}
</td>
<td>
{% if user.username != username %}
{% if user.is_blocked %}
<button onclick="unblockUser({{ user.id }}, '{{ user.username }}')" class="btn btn-success btn-small">
✅ Unblock
</button>
{% else %}
<button onclick="blockUser({{ user.id }}, '{{ user.username }}')" class="btn btn-secondary btn-small">
🚫 Block
</button>
{% endif %}
<button onclick="deleteUser({{ user.id }}, '{{ user.username }}')" class="btn btn-danger btn-small">
🗑️ Delete
</button>
{% else %}
<span style="color: #6b7280; font-size: 12px;">Current User</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!-- Create User Modal -->
<div id="createUserModal" class="modal">
<div class="modal-content">
<div class="modal-header">Create New User</div>
<form id="createUserForm">
<div class="form-group">
<label for="new_username">Username</label>
<input type="text" id="new_username" name="username" required minlength="3" maxlength="50">
</div>
<div class="form-group">
<label for="new_password">Password</label>
<input type="password" id="new_password" name="password" required minlength="8">
<small style="color: #6b7280;">Minimum 8 characters</small>
</div>
<div class="form-group">
<label for="confirm_new_password">Confirm Password</label>
<input type="password" id="confirm_new_password" required>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" id="is_admin" name="is_admin">
<label for="is_admin" style="margin: 0;">Administrator privileges</label>
</div>
</div>
<div class="form-actions">
<button type="button" onclick="closeCreateUserModal()" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Create User</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function openCreateUserModal() {
document.getElementById('createUserModal').classList.add('active');
}
function closeCreateUserModal() {
document.getElementById('createUserModal').classList.remove('active');
document.getElementById('createUserForm').reset();
}
document.getElementById('createUserForm').addEventListener('submit', async (e) => {
e.preventDefault();
const password = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_new_password').value;
if (password !== confirmPassword) {
alert('Passwords do not match!');
return;
}
const formData = {
username: document.getElementById('new_username').value,
password: password,
is_admin: document.getElementById('is_admin').checked
};
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
if (response.ok) {
window.location.reload();
} else {
const error = await response.json();
alert('Failed to create user: ' + (error.detail || 'Unknown error'));
}
} catch (error) {
alert('Failed to create user: ' + error.message);
}
});
async function blockUser(userId, username) {
if (!confirm(`Are you sure you want to block user "${username}"?`)) {
return;
}
try {
const response = await fetch(`/api/users/${userId}/block?block=true`, {
method: 'PATCH'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to block user');
}
} catch (error) {
alert('Failed to block user: ' + error.message);
}
}
async function unblockUser(userId, username) {
if (!confirm(`Are you sure you want to unblock user "${username}"?`)) {
return;
}
try {
const response = await fetch(`/api/users/${userId}/block?block=false`, {
method: 'PATCH'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to unblock user');
}
} catch (error) {
alert('Failed to unblock user: ' + error.message);
}
}
async function deleteUser(userId, username) {
if (!confirm(`Are you sure you want to DELETE user "${username}"?\n\nThis will also delete all their files. This action cannot be undone!`)) {
return;
}
const confirmation = prompt(`Type "${username}" to confirm deletion:`);
if (confirmation !== username) {
alert('Deletion cancelled - username did not match');
return;
}
try {
const response = await fetch(`/api/users/${userId}`, {
method: 'DELETE'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to delete user');
}
} catch (error) {
alert('Failed to delete user: ' + error.message);
}
}
// Close modals when clicking outside
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
});
</script>
{% endblock %}

334
templates/base.html Normal file
View File

@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Das File Storage{% endblock %}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 20px 30px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
margin-bottom: 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #667eea;
font-size: 24px;
}
.user-info {
display: flex;
align-items: center;
gap: 15px;
}
.user-badge {
background: #667eea;
color: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 14px;
}
.admin-badge {
background: #f59e0b;
color: white;
padding: 4px 10px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
text-decoration: none;
display: inline-block;
transition: all 0.3s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.btn-secondary {
background: #6b7280;
color: white;
}
.btn-secondary:hover {
background: #4b5563;
}
.btn-success {
background: #10b981;
color: white;
}
.btn-success:hover {
background: #059669;
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
}
.content {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.nav-tabs {
display: flex;
gap: 10px;
margin-bottom: 30px;
border-bottom: 2px solid #e5e7eb;
}
.nav-tab {
padding: 12px 24px;
background: none;
border: none;
cursor: pointer;
font-size: 16px;
color: #6b7280;
border-bottom: 3px solid transparent;
transition: all 0.3s;
}
.nav-tab.active {
color: #667eea;
border-bottom-color: #667eea;
}
.nav-tab:hover {
color: #667eea;
}
.alert {
padding: 15px 20px;
border-radius: 6px;
margin-bottom: 20px;
}
.alert-success {
background: #d1fae5;
color: #065f46;
border: 1px solid #10b981;
}
.alert-error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #ef4444;
}
.alert-info {
background: #dbeafe;
color: #1e40af;
border: 1px solid #3b82f6;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
th {
background: #f9fafb;
font-weight: 600;
color: #374151;
}
tr:hover {
background: #f9fafb;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal.active {
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 30px;
border-radius: 10px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #111827;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #374151;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: #667eea;
}
.form-actions {
display: flex;
gap: 10px;
justify-content: flex-end;
margin-top: 20px;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: #6b7280;
}
.empty-state svg {
width: 80px;
height: 80px;
margin-bottom: 20px;
opacity: 0.5;
}
.file-size {
color: #6b7280;
font-size: 13px;
}
.share-link {
display: flex;
gap: 10px;
align-items: center;
margin-top: 10px;
}
.share-link input {
flex: 1;
padding: 8px;
border: 1px solid #d1d5db;
border-radius: 4px;
font-size: 12px;
}
.checkbox-group {
display: flex;
align-items: center;
gap: 8px;
}
.checkbox-group input[type="checkbox"] {
width: auto;
}
</style>
{% block extra_style %}{% endblock %}
</head>
<body>
<div class="container">
<div class="header">
<h1>📁 File Storage Service by -=:dAs:=-</h1>
<div class="user-info">
<div class="user-badge">
👤 {{ username }}
{% if is_admin %}
<span class="admin-badge">ADMIN</span>
{% endif %}
</div>
<a href="/logout" class="btn btn-secondary btn-small">Logout</a>
</div>
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
</div>
{% block extra_scripts %}{% endblock %}
</body>
</html>

139
templates/login.html Normal file
View File

@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Das File Storage</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.login-container {
background: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
max-width: 400px;
width: 100%;
}
.login-header {
text-align: center;
margin-bottom: 30px;
}
.login-header h1 {
color: #667eea;
font-size: 28px;
margin-bottom: 10px;
}
.login-header p {
color: #6b7280;
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #374151;
}
.form-group input {
width: 100%;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 14px;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.btn-login {
width: 100%;
padding: 12px;
background: #667eea;
color: white;
border: none;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.btn-login:hover {
background: #5568d3;
}
.alert {
padding: 12px;
border-radius: 6px;
margin-bottom: 20px;
font-size: 14px;
}
.alert-error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #ef4444;
}
.icon {
font-size: 48px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="login-container">
<div class="login-header">
<div class="icon">📁</div>
<h1>Das File Storage</h1>
<p>Sign in to your account</p>
</div>
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<form method="POST" action="/login">
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username" required autocomplete="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password" required autocomplete="current-password">
</div>
<button type="submit" class="btn-login">Sign In</button>
</form>
</div>
</body>
</html>

134
templates/settings.html Normal file
View File

@@ -0,0 +1,134 @@
{% extends "base.html" %}
{% block title %}Settings - Das File Storage{% endblock %}
{% block content %}
<div class="nav-tabs">
<a href="/dashboard" class="nav-tab">📂 My Files</a>
<a href="/settings" class="nav-tab active">⚙️ Settings</a>
{% if is_admin %}
<a href="/admin" class="nav-tab">👑 Admin Panel</a>
{% endif %}
</div>
{% if message %}
<div class="alert alert-success">
{{ message }}
</div>
{% endif %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<h2>Account Settings</h2>
<div style="display: grid; gap: 30px; margin-top: 30px;">
<!-- Change Password -->
<div style="border: 1px solid #e5e7eb; padding: 25px; border-radius: 8px;">
<h3 style="margin-bottom: 15px; color: #374151;">🔒 Change Password</h3>
<form method="POST" action="/settings/change-password">
<div class="form-group">
<label for="current_password">Current Password</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">New Password</label>
<input type="password" id="new_password" name="new_password" required minlength="8">
<small style="color: #6b7280;">Minimum 8 characters</small>
</div>
<div class="form-group">
<label for="confirm_password">Confirm New Password</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<button type="submit" class="btn btn-primary">Update Password</button>
</form>
</div>
<!-- API Key -->
<div style="border: 1px solid #e5e7eb; padding: 25px; border-radius: 8px;">
<h3 style="margin-bottom: 15px; color: #374151;">🔑 API Key</h3>
<p style="color: #6b7280; margin-bottom: 15px;">
Use this key for API authentication with the X-API-Key header.
</p>
<div style="background: #f9fafb; padding: 15px; border-radius: 6px; margin-bottom: 15px;">
<div style="display: flex; gap: 10px; align-items: center;">
<input
type="text"
id="apiKey"
value="{{ api_key }}"
readonly
style="flex: 1; padding: 10px; border: 1px solid #d1d5db; border-radius: 4px; font-family: monospace; font-size: 12px;"
>
<button onclick="copyApiKey()" class="btn btn-secondary btn-small">📋 Copy</button>
</div>
</div>
<div class="alert alert-info" style="margin-bottom: 15px;">
<strong>⚠️ Warning:</strong> Regenerating will invalidate your current API key. All applications using the old key will stop working.
</div>
<button onclick="regenerateApiKey()" class="btn btn-danger">🔄 Regenerate API Key</button>
</div>
<!-- Account Info -->
<div style="border: 1px solid #e5e7eb; padding: 25px; border-radius: 8px;">
<h3 style="margin-bottom: 15px; color: #374151;">👤 Account Information</h3>
<div style="display: grid; gap: 15px;">
<div>
<strong style="color: #6b7280;">Username:</strong>
<div style="margin-top: 5px;">{{ username }}</div>
</div>
<div>
<strong style="color: #6b7280;">Account Type:</strong>
<div style="margin-top: 5px;">
{% if is_admin %}
<span class="admin-badge">Administrator</span>
{% else %}
Regular User
{% endif %}
</div>
</div>
<div>
<strong style="color: #6b7280;">Total Files:</strong>
<div style="margin-top: 5px;">{{ file_count }} files</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function copyApiKey() {
const input = document.getElementById('apiKey');
input.select();
document.execCommand('copy');
alert('API Key copied to clipboard!');
}
async function regenerateApiKey() {
if (!confirm('Are you sure? This will invalidate your current API key!')) {
return;
}
try {
const response = await fetch('/api/users/me/api-key/regenerate', {
method: 'POST'
});
if (response.ok) {
window.location.reload();
} else {
alert('Failed to regenerate API key');
}
} catch (error) {
alert('Failed to regenerate API key: ' + error.message);
}
}
</script>
{% endblock %}

228
templates/user_files.html Normal file
View File

@@ -0,0 +1,228 @@
{% extends "base.html" %}
{% block title %}My Files - Das File Storage{% endblock %}
{% block content %}
<div class="nav-tabs">
<a href="/dashboard" class="nav-tab active">📂 My Files</a>
<a href="/settings" class="nav-tab">⚙️ Settings</a>
{% if is_admin %}
<a href="/admin" class="nav-tab">👑 Admin Panel</a>
{% endif %}
</div>
{% if message %}
<div class="alert alert-success">
{{ message }}
</div>
{% endif %}
{% if error %}
<div class="alert alert-error">
{{ error }}
</div>
{% endif %}
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>My Files</h2>
<button onclick="openUploadModal()" class="btn btn-primary">
⬆️ Upload File
</button>
</div>
{% if files %}
<table>
<thead>
<tr>
<th>Filename</th>
<th>Size</th>
<th>Uploaded</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for file in files %}
<tr>
<td>
<strong>{{ file.filename }}</strong>
<div class="file-size">{{ file.content_type or 'Unknown' }}</div>
</td>
<td class="file-size">{{ format_size(file.file_size) }}</td>
<td class="file-size">{{ file.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<button onclick="downloadFile({{ file.id }}, '{{ file.filename }}')" class="btn btn-primary btn-small">
⬇️ Download
</button>
<button onclick="showShareLink('{{ file.share_token }}')" class="btn btn-success btn-small">
🔗 Share
</button>
<button onclick="deleteFile({{ file.id }}, '{{ file.filename }}')" class="btn btn-danger btn-small">
🗑️ Delete
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="empty-state">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
</svg>
<h3>No files yet</h3>
<p>Upload your first file to get started</p>
</div>
{% endif %}
<!-- Upload Modal -->
<div id="uploadModal" class="modal">
<div class="modal-content">
<div class="modal-header">Upload File</div>
<form id="uploadForm" enctype="multipart/form-data">
<div class="form-group">
<label for="fileInput">Choose file</label>
<input type="file" id="fileInput" name="file" required>
</div>
<div class="form-actions">
<button type="button" onclick="closeUploadModal()" class="btn btn-secondary">Cancel</button>
<button type="submit" class="btn btn-primary">Upload</button>
</div>
</form>
<div id="uploadProgress" style="display: none; margin-top: 20px;">
<div style="background: #e5e7eb; height: 20px; border-radius: 10px; overflow: hidden;">
<div id="progressBar" style="background: #667eea; height: 100%; width: 0%; transition: width 0.3s;"></div>
</div>
<p style="text-align: center; margin-top: 10px; color: #6b7280;">Uploading...</p>
</div>
</div>
</div>
<!-- Share Modal -->
<div id="shareModal" class="modal">
<div class="modal-content">
<div class="modal-header">Share File</div>
<p style="margin-bottom: 15px; color: #6b7280;">Anyone with this link can download the file:</p>
<div class="share-link">
<input type="text" id="shareLink" readonly>
<button onclick="copyShareLink()" class="btn btn-primary btn-small">📋 Copy</button>
</div>
<div class="form-actions" style="margin-top: 20px;">
<button onclick="closeShareModal()" class="btn btn-secondary">Close</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function openUploadModal() {
document.getElementById('uploadModal').classList.add('active');
}
function closeUploadModal() {
document.getElementById('uploadModal').classList.remove('active');
document.getElementById('uploadForm').reset();
document.getElementById('uploadProgress').style.display = 'none';
}
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (!file) return;
const formData = new FormData();
formData.append('file', file);
document.getElementById('uploadProgress').style.display = 'block';
try {
const response = await fetch('/api/files/upload', {
method: 'POST',
body: formData
});
if (response.ok) {
window.location.reload();
} else {
const error = await response.json();
alert('Upload failed: ' + (error.detail || 'Unknown error'));
document.getElementById('uploadProgress').style.display = 'none';
}
} catch (error) {
alert('Upload failed: ' + error.message);
document.getElementById('uploadProgress').style.display = 'none';
}
});
async function downloadFile(fileId, filename) {
try {
const response = await fetch(`/api/files/${fileId}/download`);
if (response.ok) {
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
} else {
alert('Download failed');
}
} catch (error) {
alert('Download failed: ' + error.message);
}
}
function showShareLink(shareToken) {
const shareLink = window.location.origin + '/share/' + shareToken;
document.getElementById('shareLink').value = shareLink;
document.getElementById('shareModal').classList.add('active');
}
function closeShareModal() {
document.getElementById('shareModal').classList.remove('active');
}
function copyShareLink() {
const input = document.getElementById('shareLink');
input.select();
document.execCommand('copy');
alert('Link copied to clipboard!');
}
async function deleteFile(fileId, filename) {
if (!confirm(`Are you sure you want to delete "${filename}"?`)) {
return;
}
try {
const response = await fetch(`/api/files/${fileId}`, {
method: 'DELETE'
});
if (response.ok) {
window.location.reload();
} else {
alert('Delete failed');
}
} catch (error) {
alert('Delete failed: ' + error.message);
}
}
// Close modals when clicking outside
document.querySelectorAll('.modal').forEach(modal => {
modal.addEventListener('click', (e) => {
if (e.target === modal) {
modal.classList.remove('active');
}
});
});
</script>
{% endblock %}