712 lines
32 KiB
HTML
712 lines
32 KiB
HTML
{% extends 'base.html' %}
|
|
{% load static %}
|
|
|
|
{% block title %}{% if object %}Edit{% else %}Add{% endif %} 2FA Device - Account Security{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="content">
|
|
<div class="container-fluid">
|
|
<!-- Page Header -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="page-header">
|
|
<div class="page-title">
|
|
<h4>
|
|
<a href="{% url 'accounts:two_factor_device_list' %}" class="me-3">
|
|
<i class="fas fa-arrow-left"></i>
|
|
</a>
|
|
{% if object %}Edit{% else %}Add{% endif %} 2FA Device
|
|
</h4>
|
|
<h6>{% if object %}Update{% else %}Configure{% endif %} two-factor authentication device</h6>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Device Setup Form -->
|
|
<div class="col-lg-8 col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-shield-alt me-2"></i>
|
|
Device Configuration
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="post" id="deviceForm">
|
|
{% csrf_token %}
|
|
|
|
<!-- Device Type Selection -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<label class="form-label">Device Type <span class="text-danger">*</span></label>
|
|
<div class="device-type-selection">
|
|
<div class="row">
|
|
<div class="col-md-4">
|
|
<div class="device-type-card" data-type="authenticator">
|
|
<input type="radio" name="device_type" value="authenticator" id="type_authenticator"
|
|
{% if not object or object.device_type == 'authenticator' %}checked{% endif %}>
|
|
<label for="type_authenticator" class="device-type-label">
|
|
<i class="fas fa-mobile-alt fa-3x text-primary mb-3"></i>
|
|
<h6>Authenticator App</h6>
|
|
<p class="text-muted small">Google Authenticator, Authy, etc.</p>
|
|
<span class="badge bg-primary">Recommended</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="device-type-card" data-type="hardware_key">
|
|
<input type="radio" name="device_type" value="hardware_key" id="type_hardware_key"
|
|
{% if object.device_type == 'hardware_key' %}checked{% endif %}>
|
|
<label for="type_hardware_key" class="device-type-label">
|
|
<i class="fas fa-key fa-3x text-success mb-3"></i>
|
|
<h6>Hardware Key</h6>
|
|
<p class="text-muted small">YubiKey, Titan Key, etc.</p>
|
|
<span class="badge bg-success">Most Secure</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4">
|
|
<div class="device-type-card" data-type="sms">
|
|
<input type="radio" name="device_type" value="sms" id="type_sms"
|
|
{% if object.device_type == 'sms' %}checked{% endif %}>
|
|
<label for="type_sms" class="device-type-label">
|
|
<i class="fas fa-sms fa-3x text-warning mb-3"></i>
|
|
<h6>SMS</h6>
|
|
<p class="text-muted small">Text message codes</p>
|
|
<span class="badge bg-warning">Backup Only</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Basic Information -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="device_name" class="form-label">Device Name <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="device_name" name="name"
|
|
value="{{ object.name|default:'' }}"
|
|
placeholder="e.g., My iPhone, Work YubiKey" required>
|
|
<small class="form-text text-muted">Choose a name to easily identify this device</small>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="device_description" class="form-label">Description</label>
|
|
<input type="text" class="form-control" id="device_description" name="description"
|
|
value="{{ object.description|default:'' }}"
|
|
placeholder="Optional description">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Authenticator App Setup -->
|
|
<div id="authenticator_setup" class="setup-section" style="display: none;">
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
<strong>Setup Instructions:</strong> Scan the QR code below with your authenticator app, then enter the verification code.
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6 text-center">
|
|
<div class="qr-code-container mb-3">
|
|
<div class="qr-code-placeholder">
|
|
<i class="fas fa-qrcode fa-5x text-muted mb-3"></i>
|
|
<p class="text-muted">QR Code will appear here</p>
|
|
<button type="button" class="btn btn-primary btn-sm" onclick="generateQRCode()">
|
|
<i class="fas fa-sync me-1"></i>Generate QR Code
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="manual-entry">
|
|
<small class="text-muted">Can't scan? Enter manually:</small>
|
|
<div class="input-group input-group-sm mt-2">
|
|
<input type="text" class="form-control" id="manual_key" readonly
|
|
value="JBSWY3DPEHPK3PXP" placeholder="Secret key">
|
|
<button class="btn btn-outline-secondary" type="button" onclick="copySecret()">
|
|
<i class="fas fa-copy"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="verification_code" class="form-label">Verification Code <span class="text-danger">*</span></label>
|
|
<input type="text" class="form-control" id="verification_code" name="verification_code"
|
|
placeholder="Enter 6-digit code" maxlength="6" pattern="[0-9]{6}">
|
|
<small class="form-text text-muted">Enter the 6-digit code from your authenticator app</small>
|
|
</div>
|
|
<div class="verification-status mt-3" id="verification_status" style="display: none;">
|
|
<!-- Verification status will be shown here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Hardware Key Setup -->
|
|
<div id="hardware_key_setup" class="setup-section" style="display: none;">
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle me-2"></i>
|
|
<strong>Setup Instructions:</strong> Insert your hardware key and click the button below to register it.
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<div class="hardware-key-status mb-4">
|
|
<i class="fas fa-key fa-4x text-muted mb-3"></i>
|
|
<p class="text-muted">Insert your hardware security key</p>
|
|
</div>
|
|
<button type="button" class="btn btn-primary" onclick="registerHardwareKey()">
|
|
<i class="fas fa-usb me-2"></i>Register Hardware Key
|
|
</button>
|
|
<div class="registration-status mt-3" id="hardware_status" style="display: none;">
|
|
<!-- Registration status will be shown here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- SMS Setup -->
|
|
<div id="sms_setup" class="setup-section" style="display: none;">
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Note:</strong> SMS is less secure than other methods and should only be used as a backup option.
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="phone_number" class="form-label">Phone Number <span class="text-danger">*</span></label>
|
|
<input type="tel" class="form-control" id="phone_number" name="phone_number"
|
|
value="{{ object.phone_number|default:'' }}"
|
|
placeholder="+1 (555) 123-4567">
|
|
<small class="form-text text-muted">Include country code</small>
|
|
</div>
|
|
<button type="button" class="btn btn-outline-primary" onclick="sendTestSMS()">
|
|
<i class="fas fa-paper-plane me-1"></i>Send Test Code
|
|
</button>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="form-group">
|
|
<label for="sms_verification_code" class="form-label">Verification Code</label>
|
|
<input type="text" class="form-control" id="sms_verification_code" name="sms_verification_code"
|
|
placeholder="Enter code from SMS" maxlength="6" pattern="[0-9]{6}">
|
|
<small class="form-text text-muted">Enter the code sent to your phone</small>
|
|
</div>
|
|
<div class="sms-status mt-3" id="sms_status" style="display: none;">
|
|
<!-- SMS verification status will be shown here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Additional Options -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="is_primary" name="is_primary"
|
|
{% if object.is_primary %}checked{% endif %}>
|
|
<label class="form-check-label" for="is_primary">
|
|
Set as primary 2FA device
|
|
</label>
|
|
<small class="form-text text-muted d-block">Primary device will be used first for authentication</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" id="generate_backup_codes" name="generate_backup_codes"
|
|
{% if not object %}checked{% endif %}>
|
|
<label class="form-check-label" for="generate_backup_codes">
|
|
Generate backup codes
|
|
</label>
|
|
<small class="form-text text-muted d-block">Backup codes can be used if your device is unavailable</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="row mt-5">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between">
|
|
<a href="{% url 'accounts:two_factor_device_list' %}" class="btn btn-secondary">
|
|
<i class="fas fa-times me-1"></i>Cancel
|
|
</a>
|
|
<div>
|
|
<button type="button" class="btn btn-outline-primary me-2" onclick="testConfiguration()">
|
|
<i class="fas fa-vial me-1"></i>Test Configuration
|
|
</button>
|
|
<button type="submit" class="btn btn-primary" id="submitBtn">
|
|
<i class="fas fa-save me-1"></i>
|
|
{% if object %}Update{% else %}Add{% endif %} Device
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Setup Guide Sidebar -->
|
|
<div class="col-lg-4 col-md-12">
|
|
<!-- Setup Progress -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-list-check me-2"></i>
|
|
Setup Progress
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="setup-steps">
|
|
<div class="step-item active" id="step1">
|
|
<div class="step-number">1</div>
|
|
<div class="step-content">
|
|
<h6>Choose Device Type</h6>
|
|
<small class="text-muted">Select your preferred 2FA method</small>
|
|
</div>
|
|
</div>
|
|
<div class="step-item" id="step2">
|
|
<div class="step-number">2</div>
|
|
<div class="step-content">
|
|
<h6>Configure Device</h6>
|
|
<small class="text-muted">Set up your authentication device</small>
|
|
</div>
|
|
</div>
|
|
<div class="step-item" id="step3">
|
|
<div class="step-number">3</div>
|
|
<div class="step-content">
|
|
<h6>Verify Setup</h6>
|
|
<small class="text-muted">Test your device configuration</small>
|
|
</div>
|
|
</div>
|
|
<div class="step-item" id="step4">
|
|
<div class="step-number">4</div>
|
|
<div class="step-content">
|
|
<h6>Complete</h6>
|
|
<small class="text-muted">Save your 2FA device</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Security Information -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-shield-alt me-2"></i>
|
|
Security Levels
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="security-level mb-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas fa-key text-success me-2"></i>
|
|
<strong>Hardware Key</strong>
|
|
</div>
|
|
<span class="badge bg-success">High</span>
|
|
</div>
|
|
<small class="text-muted d-block mt-1">Phishing-resistant, most secure option</small>
|
|
</div>
|
|
<div class="security-level mb-3">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas fa-mobile-alt text-primary me-2"></i>
|
|
<strong>Authenticator App</strong>
|
|
</div>
|
|
<span class="badge bg-primary">Medium</span>
|
|
</div>
|
|
<small class="text-muted d-block mt-1">Time-based codes, good security</small>
|
|
</div>
|
|
<div class="security-level">
|
|
<div class="d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<i class="fas fa-sms text-warning me-2"></i>
|
|
<strong>SMS</strong>
|
|
</div>
|
|
<span class="badge bg-warning">Basic</span>
|
|
</div>
|
|
<small class="text-muted d-block mt-1">Vulnerable to SIM swapping attacks</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Popular Apps -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5 class="card-title mb-0">
|
|
<i class="fas fa-mobile-alt me-2"></i>
|
|
Recommended Apps
|
|
</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="app-recommendation mb-3">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fab fa-google text-primary fa-2x me-3"></i>
|
|
<div>
|
|
<h6 class="mb-0">Google Authenticator</h6>
|
|
<small class="text-muted">Free, simple, reliable</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="app-recommendation mb-3">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-shield-alt text-success fa-2x me-3"></i>
|
|
<div>
|
|
<h6 class="mb-0">Authy</h6>
|
|
<small class="text-muted">Cloud backup, multi-device</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="app-recommendation">
|
|
<div class="d-flex align-items-center">
|
|
<i class="fas fa-lock text-info fa-2x me-3"></i>
|
|
<div>
|
|
<h6 class="mb-0">1Password</h6>
|
|
<small class="text-muted">Integrated with password manager</small>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Initialize device type selection
|
|
const deviceTypeRadios = document.querySelectorAll('input[name="device_type"]');
|
|
deviceTypeRadios.forEach(radio => {
|
|
radio.addEventListener('change', function() {
|
|
showSetupSection(this.value);
|
|
updateStepProgress(2);
|
|
});
|
|
});
|
|
|
|
// Show initial setup section
|
|
const checkedRadio = document.querySelector('input[name="device_type"]:checked');
|
|
if (checkedRadio) {
|
|
showSetupSection(checkedRadio.value);
|
|
}
|
|
|
|
// Form validation
|
|
const form = document.getElementById('deviceForm');
|
|
form.addEventListener('submit', function(e) {
|
|
if (!validateForm()) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
|
|
function showSetupSection(deviceType) {
|
|
// Hide all setup sections
|
|
const sections = document.querySelectorAll('.setup-section');
|
|
sections.forEach(section => section.style.display = 'none');
|
|
|
|
// Show selected section
|
|
const targetSection = document.getElementById(deviceType + '_setup');
|
|
if (targetSection) {
|
|
targetSection.style.display = 'block';
|
|
}
|
|
|
|
// Update device type cards
|
|
const cards = document.querySelectorAll('.device-type-card');
|
|
cards.forEach(card => card.classList.remove('selected'));
|
|
|
|
const selectedCard = document.querySelector(`[data-type="${deviceType}"]`);
|
|
if (selectedCard) {
|
|
selectedCard.classList.add('selected');
|
|
}
|
|
}
|
|
|
|
function updateStepProgress(step) {
|
|
const steps = document.querySelectorAll('.step-item');
|
|
steps.forEach((stepItem, index) => {
|
|
if (index < step) {
|
|
stepItem.classList.add('completed');
|
|
stepItem.classList.remove('active');
|
|
} else if (index === step - 1) {
|
|
stepItem.classList.add('active');
|
|
stepItem.classList.remove('completed');
|
|
} else {
|
|
stepItem.classList.remove('active', 'completed');
|
|
}
|
|
});
|
|
}
|
|
|
|
function generateQRCode() {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Generating...';
|
|
button.disabled = true;
|
|
|
|
setTimeout(() => {
|
|
// Simulate QR code generation
|
|
const container = document.querySelector('.qr-code-placeholder');
|
|
container.innerHTML = `
|
|
<div class="qr-code-display">
|
|
<div style="width: 200px; height: 200px; background: #000; margin: 0 auto; display: flex; align-items: center; justify-content: center; color: white;">
|
|
QR CODE
|
|
</div>
|
|
<p class="text-success mt-2"><i class="fas fa-check me-1"></i>QR Code Generated</p>
|
|
</div>
|
|
`;
|
|
updateStepProgress(3);
|
|
}, 2000);
|
|
}
|
|
|
|
function copySecret() {
|
|
const secretInput = document.getElementById('manual_key');
|
|
secretInput.select();
|
|
document.execCommand('copy');
|
|
|
|
const button = event.target.closest('button');
|
|
const originalIcon = button.innerHTML;
|
|
button.innerHTML = '<i class="fas fa-check"></i>';
|
|
|
|
setTimeout(() => {
|
|
button.innerHTML = originalIcon;
|
|
}, 2000);
|
|
}
|
|
|
|
function registerHardwareKey() {
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
const statusDiv = document.getElementById('hardware_status');
|
|
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Registering...';
|
|
button.disabled = true;
|
|
|
|
statusDiv.style.display = 'block';
|
|
statusDiv.innerHTML = '<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>Touch your security key now...</div>';
|
|
|
|
setTimeout(() => {
|
|
statusDiv.innerHTML = '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>Hardware key registered successfully!</div>';
|
|
button.innerHTML = '<i class="fas fa-check me-2"></i>Registered';
|
|
updateStepProgress(3);
|
|
}, 3000);
|
|
}
|
|
|
|
function sendTestSMS() {
|
|
const phoneInput = document.getElementById('phone_number');
|
|
const button = event.target;
|
|
const originalText = button.innerHTML;
|
|
|
|
if (!phoneInput.value) {
|
|
alert('Please enter a phone number first.');
|
|
return;
|
|
}
|
|
|
|
button.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Sending...';
|
|
button.disabled = true;
|
|
|
|
setTimeout(() => {
|
|
const statusDiv = document.getElementById('sms_status');
|
|
statusDiv.style.display = 'block';
|
|
statusDiv.innerHTML = '<div class="alert alert-success"><i class="fas fa-check-circle me-2"></i>Test code sent to your phone!</div>';
|
|
|
|
button.innerHTML = originalText;
|
|
button.disabled = false;
|
|
updateStepProgress(3);
|
|
}, 2000);
|
|
}
|
|
|
|
function testConfiguration() {
|
|
const deviceType = document.querySelector('input[name="device_type"]:checked').value;
|
|
|
|
alert(`Testing ${deviceType} configuration...`);
|
|
updateStepProgress(4);
|
|
}
|
|
|
|
function validateForm() {
|
|
const deviceType = document.querySelector('input[name="device_type"]:checked');
|
|
const deviceName = document.getElementById('device_name').value;
|
|
|
|
if (!deviceType) {
|
|
alert('Please select a device type.');
|
|
return false;
|
|
}
|
|
|
|
if (!deviceName.trim()) {
|
|
alert('Please enter a device name.');
|
|
return false;
|
|
}
|
|
|
|
// Additional validation based on device type
|
|
if (deviceType.value === 'authenticator') {
|
|
const verificationCode = document.getElementById('verification_code').value;
|
|
if (!verificationCode || verificationCode.length !== 6) {
|
|
alert('Please enter a valid 6-digit verification code.');
|
|
return false;
|
|
}
|
|
} else if (deviceType.value === 'sms') {
|
|
const phoneNumber = document.getElementById('phone_number').value;
|
|
if (!phoneNumber.trim()) {
|
|
alert('Please enter a phone number.');
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.device-type-selection {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.device-type-card {
|
|
border: 2px solid #e9ecef;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
height: 100%;
|
|
}
|
|
|
|
.device-type-card:hover {
|
|
border-color: #007bff;
|
|
box-shadow: 0 4px 8px rgba(0,123,255,0.1);
|
|
}
|
|
|
|
.device-type-card.selected {
|
|
border-color: #007bff;
|
|
background-color: #f8f9ff;
|
|
}
|
|
|
|
.device-type-card input[type="radio"] {
|
|
display: none;
|
|
}
|
|
|
|
.device-type-label {
|
|
cursor: pointer;
|
|
margin: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
display: block;
|
|
}
|
|
|
|
.setup-section {
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 10px;
|
|
padding: 20px;
|
|
margin-top: 20px;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.qr-code-container {
|
|
border: 2px dashed #dee2e6;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
background-color: white;
|
|
}
|
|
|
|
.qr-code-placeholder {
|
|
text-align: center;
|
|
}
|
|
|
|
.setup-steps {
|
|
position: relative;
|
|
}
|
|
|
|
.step-item {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
position: relative;
|
|
}
|
|
|
|
.step-item:not(:last-child)::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 15px;
|
|
top: 40px;
|
|
width: 2px;
|
|
height: 30px;
|
|
background-color: #dee2e6;
|
|
}
|
|
|
|
.step-item.completed::after {
|
|
background-color: #28a745;
|
|
}
|
|
|
|
.step-item.active::after {
|
|
background-color: #007bff;
|
|
}
|
|
|
|
.step-number {
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
background-color: #dee2e6;
|
|
color: #6c757d;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
margin-right: 15px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.step-item.active .step-number {
|
|
background-color: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.step-item.completed .step-number {
|
|
background-color: #28a745;
|
|
color: white;
|
|
}
|
|
|
|
.step-item.completed .step-number::before {
|
|
content: '✓';
|
|
font-size: 14px;
|
|
}
|
|
|
|
.step-item.completed .step-number {
|
|
font-size: 0;
|
|
}
|
|
|
|
.security-level,
|
|
.app-recommendation {
|
|
padding: 10px 0;
|
|
border-bottom: 1px solid #eee;
|
|
}
|
|
|
|
.security-level:last-child,
|
|
.app-recommendation:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.device-type-card {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.setup-section {
|
|
margin-top: 15px;
|
|
padding: 15px;
|
|
}
|
|
|
|
.qr-code-container {
|
|
padding: 20px;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|