wip
This commit is contained in:
parent
eb93961967
commit
ea3e92b8b7
10 changed files with 773 additions and 167 deletions
|
@ -1,4 +1,4 @@
|
|||
from flask import render_template, redirect, url_for, flash, request
|
||||
from flask import render_template, redirect, url_for, flash, request, current_app, jsonify, session
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from urllib.parse import urlparse
|
||||
from app import db
|
||||
|
@ -7,6 +7,7 @@ from app.routes import auth_bp
|
|||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||
from wtforms.validators import DataRequired, Length, EqualTo, ValidationError
|
||||
from werkzeug.exceptions import BadRequest
|
||||
|
||||
# Login form
|
||||
class LoginForm(FlaskForm):
|
||||
|
@ -73,7 +74,74 @@ def register():
|
|||
|
||||
return render_template('auth/register.html', title='Register', form=form)
|
||||
|
||||
@auth_bp.route('/update_profile', methods=['POST'])
|
||||
@login_required
|
||||
def update_profile():
|
||||
"""Update user profile information"""
|
||||
username = request.form.get('username')
|
||||
|
||||
if not username or username.strip() == '':
|
||||
flash('Username cannot be empty', 'error')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
# Check if username is already taken by another user
|
||||
existing_user = User.query.filter(User.username == username, User.id != current_user.id).first()
|
||||
if existing_user:
|
||||
flash('Username is already taken', 'error')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
# Update username
|
||||
current_user.username = username
|
||||
db.session.commit()
|
||||
flash('Profile updated successfully', 'success')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
@auth_bp.route('/change_password', methods=['POST'])
|
||||
@login_required
|
||||
def change_password():
|
||||
"""Change user password"""
|
||||
current_password = request.form.get('current_password')
|
||||
new_password = request.form.get('new_password')
|
||||
confirm_password = request.form.get('confirm_password')
|
||||
|
||||
# Validate input
|
||||
if not all([current_password, new_password, confirm_password]):
|
||||
flash('All fields are required', 'error')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
if new_password != confirm_password:
|
||||
flash('New passwords do not match', 'error')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
# Check current password
|
||||
if not current_user.check_password(current_password):
|
||||
flash('Current password is incorrect', 'error')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
# Set new password
|
||||
current_user.set_password(new_password)
|
||||
db.session.commit()
|
||||
flash('Password changed successfully', 'success')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
@auth_bp.route('/update_preferences', methods=['POST'])
|
||||
@login_required
|
||||
def update_preferences():
|
||||
"""Update user preferences like theme"""
|
||||
theme_preference = request.form.get('theme_preference', 'system')
|
||||
|
||||
# Store in session for now, but could be added to user model
|
||||
session['theme_preference'] = theme_preference
|
||||
|
||||
flash('Preferences updated successfully', 'success')
|
||||
return redirect(url_for('auth.profile'))
|
||||
|
||||
@auth_bp.route('/profile')
|
||||
@login_required
|
||||
def profile():
|
||||
return render_template('auth/profile.html', title='User Profile')
|
||||
# Get theme preference from session or default to system
|
||||
theme_preference = session.get('theme_preference', 'system')
|
||||
|
||||
return render_template('auth/profile.html',
|
||||
title='User Profile',
|
||||
theme_preference=theme_preference)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from flask import render_template
|
||||
from flask import Blueprint, render_template
|
||||
from flask_login import login_required, current_user
|
||||
from app.routes import dashboard_bp
|
||||
from app.models import File, Share
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from app.models import File, Share, Download
|
||||
import os
|
||||
|
||||
dashboard_bp = Blueprint('dashboard', __name__)
|
||||
|
||||
@dashboard_bp.route('/')
|
||||
@login_required
|
||||
|
@ -10,15 +12,102 @@ def index():
|
|||
# Get some stats for the dashboard
|
||||
total_files = File.query.filter_by(user_id=current_user.id, is_folder=False).count()
|
||||
total_folders = File.query.filter_by(user_id=current_user.id, is_folder=True).count()
|
||||
recent_files = File.query.filter_by(user_id=current_user.id, is_folder=False).order_by(File.updated_at.desc()).limit(5).all()
|
||||
active_shares = Share.query.filter_by(user_id=current_user.id).filter(
|
||||
(Share.expires_at > datetime.now()) | (Share.expires_at.is_(None))
|
||||
).count()
|
||||
|
||||
# Recent files for quick access
|
||||
recent_files = File.query.filter_by(user_id=current_user.id, is_folder=False)\
|
||||
.order_by(File.updated_at.desc())\
|
||||
.limit(8).all()
|
||||
|
||||
# Root folders for quick navigation
|
||||
root_folders = File.query.filter_by(user_id=current_user.id, is_folder=True, parent_id=None)\
|
||||
.order_by(File.name)\
|
||||
.limit(8).all()
|
||||
|
||||
# Count active shares (if Share model exists)
|
||||
active_shares = 0
|
||||
recent_activities = 0
|
||||
|
||||
# Check if Share and Download models exist/are imported
|
||||
try:
|
||||
# Count active shares
|
||||
active_shares = Share.query.filter_by(user_id=current_user.id).filter(
|
||||
(Share.expires_at > datetime.now()) | (Share.expires_at.is_(None))
|
||||
).count()
|
||||
|
||||
# Recent activities count (downloads, shares, etc.)
|
||||
recent_activities = Download.query.join(Share)\
|
||||
.filter(Share.user_id == current_user.id)\
|
||||
.filter(Download.timestamp > (datetime.now() - timedelta(days=7)))\
|
||||
.count()
|
||||
except:
|
||||
# Models not ready yet, using default values
|
||||
pass
|
||||
|
||||
return render_template('dashboard.html',
|
||||
title='Dashboard',
|
||||
total_files=total_files,
|
||||
total_folders=total_folders,
|
||||
recent_files=recent_files,
|
||||
root_folders=root_folders,
|
||||
active_shares=active_shares,
|
||||
now=datetime.now())
|
||||
recent_activities=recent_activities,
|
||||
now=datetime.now(),
|
||||
file_icon=get_file_icon,
|
||||
format_size=format_file_size)
|
||||
|
||||
def get_file_icon(mime_type, filename):
|
||||
"""Return Font Awesome icon class based on file type"""
|
||||
if mime_type:
|
||||
if mime_type.startswith('image/'):
|
||||
return 'fa-file-image'
|
||||
elif mime_type.startswith('video/'):
|
||||
return 'fa-file-video'
|
||||
elif mime_type.startswith('audio/'):
|
||||
return 'fa-file-audio'
|
||||
elif mime_type.startswith('text/'):
|
||||
return 'fa-file-alt'
|
||||
elif mime_type.startswith('application/pdf'):
|
||||
return 'fa-file-pdf'
|
||||
elif 'spreadsheet' in mime_type or 'excel' in mime_type:
|
||||
return 'fa-file-excel'
|
||||
elif 'presentation' in mime_type or 'powerpoint' in mime_type:
|
||||
return 'fa-file-powerpoint'
|
||||
elif 'document' in mime_type or 'word' in mime_type:
|
||||
return 'fa-file-word'
|
||||
|
||||
# Check by extension
|
||||
ext = os.path.splitext(filename)[1].lower()[1:]
|
||||
if ext in ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'svg', 'webp']:
|
||||
return 'fa-file-image'
|
||||
elif ext in ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv']:
|
||||
return 'fa-file-video'
|
||||
elif ext in ['mp3', 'wav', 'ogg', 'flac', 'm4a']:
|
||||
return 'fa-file-audio'
|
||||
elif ext in ['doc', 'docx', 'odt']:
|
||||
return 'fa-file-word'
|
||||
elif ext in ['xls', 'xlsx', 'ods', 'csv']:
|
||||
return 'fa-file-excel'
|
||||
elif ext in ['ppt', 'pptx', 'odp']:
|
||||
return 'fa-file-powerpoint'
|
||||
elif ext == 'pdf':
|
||||
return 'fa-file-pdf'
|
||||
elif ext in ['zip', 'rar', '7z', 'tar', 'gz']:
|
||||
return 'fa-file-archive'
|
||||
elif ext in ['txt', 'rtf', 'md']:
|
||||
return 'fa-file-alt'
|
||||
elif ext in ['html', 'css', 'js', 'py', 'java', 'php', 'c', 'cpp', 'json', 'xml']:
|
||||
return 'fa-file-code'
|
||||
|
||||
return 'fa-file'
|
||||
|
||||
def format_file_size(size):
|
||||
"""Format file size in bytes to human-readable format"""
|
||||
if not size:
|
||||
return "0 B"
|
||||
|
||||
size_names = ("B", "KB", "MB", "GB", "TB")
|
||||
i = 0
|
||||
while size >= 1024 and i < len(size_names) - 1:
|
||||
size /= 1024
|
||||
i += 1
|
||||
return f"{size:.1f} {size_names[i]}"
|
|
@ -1,4 +1,4 @@
|
|||
from flask import render_template, redirect, url_for, flash, request, send_from_directory, abort, jsonify
|
||||
from flask import render_template, redirect, url_for, flash, request, send_from_directory, abort, jsonify, send_file
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.utils import secure_filename
|
||||
from app import db
|
||||
|
@ -581,4 +581,68 @@ def upload_xhr():
|
|||
'errors': errors
|
||||
}
|
||||
|
||||
return jsonify(result)
|
||||
return jsonify(result)
|
||||
|
||||
@files_bp.route('/contents')
|
||||
@login_required
|
||||
def folder_contents():
|
||||
"""Returns the HTML for folder contents (used for AJAX loading)"""
|
||||
folder_id = request.args.get('folder_id', None, type=int)
|
||||
|
||||
if request.headers.get('X-Requested-With') != 'XMLHttpRequest':
|
||||
# If not an AJAX request, redirect to browser view
|
||||
return redirect(url_for('files.browser', folder_id=folder_id))
|
||||
|
||||
# Query parent folder
|
||||
parent_folder = None
|
||||
if folder_id:
|
||||
parent_folder = File.query.filter_by(id=folder_id, user_id=current_user.id, is_folder=True).first_or_404()
|
||||
|
||||
# Get files and subfolders
|
||||
query = File.query.filter_by(user_id=current_user.id, parent_id=folder_id)
|
||||
folders = query.filter_by(is_folder=True).order_by(File.name).all()
|
||||
files = query.filter_by(is_folder=False).order_by(File.name).all()
|
||||
|
||||
return render_template('files/partials/folder_contents.html',
|
||||
folders=folders,
|
||||
files=files,
|
||||
parent=parent_folder)
|
||||
|
||||
@files_bp.route('/preview/<int:file_id>')
|
||||
@login_required
|
||||
def file_preview(file_id):
|
||||
"""Returns file preview data"""
|
||||
file = File.query.filter_by(id=file_id, user_id=current_user.id, is_folder=False).first_or_404()
|
||||
|
||||
result = {
|
||||
'success': True,
|
||||
'file': {
|
||||
'id': file.id,
|
||||
'name': file.name,
|
||||
'mime_type': file.mime_type,
|
||||
'size': file.size,
|
||||
'created_at': file.created_at.isoformat() if file.created_at else None,
|
||||
'updated_at': file.updated_at.isoformat() if file.updated_at else None
|
||||
},
|
||||
'download_url': url_for('files.download', file_id=file.id),
|
||||
'preview_url': url_for('files.raw', file_id=file.id)
|
||||
}
|
||||
|
||||
return jsonify(result)
|
||||
|
||||
@files_bp.route('/raw/<int:file_id>')
|
||||
@login_required
|
||||
def raw(file_id):
|
||||
"""Serves raw file content for previews"""
|
||||
file = File.query.filter_by(id=file_id, user_id=current_user.id, is_folder=False).first_or_404()
|
||||
|
||||
# Check if file exists
|
||||
file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], file.storage_name)
|
||||
if not os.path.exists(file_path):
|
||||
abort(404)
|
||||
|
||||
# Check file size for text files (only preview if under 2MB)
|
||||
if file.mime_type and file.mime_type.startswith('text/') and file.size > 2 * 1024 * 1024:
|
||||
return "File too large to preview", 413
|
||||
|
||||
return send_file(file_path, mimetype=file.mime_type)
|
Loading…
Add table
Add a link
Reference in a new issue