| {% 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">
|
|
|
| <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>
|
|
|
|
|
| <div class="dashboard-card mt-4">
|
| <h2 class="h5 mb-4">Security Settings</h2>
|
|
|
|
|
| <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>
|
|
|
|
|
| <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>
|
|
|
|
|
| <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>
|
|
|
|
|
| <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>
|
|
|
|
|
| <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>
|
|
|
|
|
| <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 %}
|
|
|