Spaces:
Runtime error
Runtime error
| {% extends "base.html" %} | |
| {% block title %}User Profile - Outline VPN{% endblock %} | |
| {% block content %} | |
| <div class="dashboard-container"> | |
| {% include 'sidebar.html' %} | |
| <main class="main-content"> | |
| <div class="container"> | |
| <h1 class="h3 mb-4">User Profile</h1> | |
| <div class="row"> | |
| <!-- Profile Information --> | |
| <div class="col-lg-8"> | |
| <div class="dashboard-card"> | |
| <div class="d-flex justify-content-between align-items-center mb-4"> | |
| <h2 class="h5 mb-0">Profile Information</h2> | |
| <button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#editProfileModal"> | |
| <i class="bi bi-pencil me-2"></i>Edit Profile | |
| </button> | |
| </div> | |
| <div class="row"> | |
| <div class="col-md-6"> | |
| <p class="text-muted mb-1">Username</p> | |
| <p class="h6">{{ current_user.username }}</p> | |
| </div> | |
| <div class="col-md-6"> | |
| <p class="text-muted mb-1">Role</p> | |
| <p class="h6">{{ current_user.role.value|title }}</p> | |
| </div> | |
| <div class="col-md-6 mt-3"> | |
| <p class="text-muted mb-1">Account Status</p> | |
| <p class="h6"> | |
| <span class="status-indicator status-{{ current_user.status.value }}"></span> | |
| {{ current_user.status.value|title }} | |
| </p> | |
| </div> | |
| <div class="col-md-6 mt-3"> | |
| <p class="text-muted mb-1">Member Since</p> | |
| <p class="h6">{{ current_user.created_at.strftime('%B %d, %Y') }}</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Security Settings --> | |
| <div class="dashboard-card mt-4"> | |
| <h2 class="h5 mb-4">Security Settings</h2> | |
| <!-- Change Password --> | |
| <div class="mb-4"> | |
| <div class="d-flex justify-content-between align-items-center mb-3"> | |
| <div> | |
| <h3 class="h6 mb-1">Password</h3> | |
| <p class="text-muted small mb-0">Last changed {{ current_user.password_changed_at.strftime('%B %d, %Y') if current_user.password_changed_at else 'Never' }}</p> | |
| </div> | |
| <button class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#changePasswordModal"> | |
| Change Password | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Two-Factor Authentication --> | |
| <div class="mb-4"> | |
| <div class="d-flex justify-content-between align-items-center mb-3"> | |
| <div> | |
| <h3 class="h6 mb-1">Two-Factor Authentication</h3> | |
| <p class="text-muted small mb-0">Add an extra layer of security to your account</p> | |
| </div> | |
| <button class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#setup2FAModal"> | |
| {% if current_user.two_factor_enabled %}Manage 2FA{% else %}Enable 2FA{% endif %} | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Active Sessions --> | |
| <div class="col-lg-4"> | |
| <div class="dashboard-card"> | |
| <h2 class="h5 mb-4">Active Sessions</h2> | |
| <div class="active-sessions"> | |
| {% for session in user_sessions %} | |
| <div class="session-item mb-3 p-3 border rounded"> | |
| <div class="d-flex justify-content-between align-items-start mb-2"> | |
| <div> | |
| <h6 class="mb-1">{{ session.device_info }}</h6> | |
| <small class="text-muted">{{ session.ip_address }}</small> | |
| </div> | |
| {% if session.is_current %} | |
| <span class="badge bg-success">Current</span> | |
| {% endif %} | |
| </div> | |
| <div class="small text-muted mb-2"> | |
| Last active: {{ session.last_active.strftime('%B %d, %Y %H:%M') }} | |
| </div> | |
| {% if not session.is_current %} | |
| <button class="btn btn-danger btn-sm" onclick="terminateSession('{{ session.id }}')"> | |
| <i class="bi bi-x-circle me-1"></i>Terminate | |
| </button> | |
| {% endif %} | |
| </div> | |
| {% endfor %} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Edit Profile Modal --> | |
| <div class="modal fade" id="editProfileModal" tabindex="-1"> | |
| <div class="modal-dialog"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title">Edit Profile</h5> | |
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
| </div> | |
| <form action="{{ url_for('update_profile') }}" method="POST"> | |
| <div class="modal-body"> | |
| <div class="mb-3"> | |
| <label class="form-label">Username</label> | |
| <input type="text" class="form-control" name="username" value="{{ current_user.username }}" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label">Email</label> | |
| <input type="email" class="form-control" name="email" value="{{ current_user.email }}" required> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> | |
| <button type="submit" class="btn btn-primary">Save Changes</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Change Password Modal --> | |
| <div class="modal fade" id="changePasswordModal" tabindex="-1"> | |
| <div class="modal-dialog"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title">Change Password</h5> | |
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
| </div> | |
| <form action="{{ url_for('change_password') }}" method="POST"> | |
| <div class="modal-body"> | |
| <div class="mb-3"> | |
| <label class="form-label">Current Password</label> | |
| <input type="password" class="form-control" name="current_password" required> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label">New Password</label> | |
| <input type="password" class="form-control" name="new_password" required minlength="8"> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="form-label">Confirm New Password</label> | |
| <input type="password" class="form-control" name="confirm_password" required minlength="8"> | |
| </div> | |
| </div> | |
| <div class="modal-footer"> | |
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button> | |
| <button type="submit" class="btn btn-primary">Change Password</button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Setup 2FA Modal --> | |
| <div class="modal fade" id="setup2FAModal" tabindex="-1"> | |
| <div class="modal-dialog"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h5 class="modal-title">Two-Factor Authentication</h5> | |
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button> | |
| </div> | |
| <div class="modal-body"> | |
| {% if not current_user.two_factor_enabled %} | |
| <div class="text-center mb-4"> | |
| <img src="{{ qr_code_url }}" alt="2FA QR Code" class="img-fluid mb-3"> | |
| <p class="mb-1">Scan this QR code with your authenticator app</p> | |
| <p class="text-muted small">or enter this code manually: <code>{{ setup_code }}</code></p> | |
| </div> | |
| <form action="{{ url_for('enable_2fa') }}" method="POST"> | |
| <div class="mb-3"> | |
| <label class="form-label">Verification Code</label> | |
| <input type="text" class="form-control" name="verification_code" required> | |
| </div> | |
| <div class="d-grid"> | |
| <button type="submit" class="btn btn-primary">Enable 2FA</button> | |
| </div> | |
| </form> | |
| {% else %} | |
| <div class="text-center mb-4"> | |
| <i class="bi bi-shield-check text-success" style="font-size: 3rem;"></i> | |
| <h6 class="mt-3">Two-Factor Authentication is Enabled</h6> | |
| </div> | |
| <form action="{{ url_for('disable_2fa') }}" method="POST"> | |
| <div class="mb-3"> | |
| <label class="form-label">Enter your password to disable 2FA</label> | |
| <input type="password" class="form-control" name="password" required> | |
| </div> | |
| <div class="d-grid"> | |
| <button type="submit" class="btn btn-danger">Disable 2FA</button> | |
| </div> | |
| </form> | |
| {% endif %} | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| {% endblock %} | |
| {% block extra_js %} | |
| <script> | |
| function terminateSession(sessionId) { | |
| if (confirm('Are you sure you want to terminate this session?')) { | |
| fetch(`/api/sessions/${sessionId}/terminate`, { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| }).then(response => { | |
| if (response.ok) { | |
| location.reload(); | |
| } | |
| }); | |
| } | |
| } | |
| </script> | |
| {% endblock %} | |