| {% extends "base.html" %}
|
|
|
| {% block title %}Server Administration - Outline VPN{% endblock %}
|
|
|
| {% block content %}
|
| <div class="dashboard-container">
|
| {% include 'sidebar.html' %}
|
|
|
| <main class="main-content">
|
| <div class="container">
|
| <div class="d-flex justify-content-between align-items-center mb-4">
|
| <h1 class="h3">Server Administration</h1>
|
| <div class="btn-group">
|
| <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#backupModal">
|
| <i class="bi bi-download me-2"></i>Backup Configuration
|
| </button>
|
| <button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#restoreModal">
|
| <i class="bi bi-upload me-2"></i>Restore
|
| </button>
|
| </div>
|
| </div>
|
|
|
| <div class="row">
|
|
|
| <div class="col-lg-8">
|
| <div class="dashboard-card">
|
| <h2 class="h5 mb-4">Server Configuration</h2>
|
| <form id="serverConfigForm" action="{{ url_for('update_server_config') }}" method="POST">
|
|
|
| <div class="mb-4">
|
| <h3 class="h6 mb-3">Network Settings</h3>
|
| <div class="row g-3">
|
| <div class="col-md-6">
|
| <label class="form-label">Server Port</label>
|
| <input type="number" class="form-control" name="port" value="{{ config.port }}" required>
|
| </div>
|
| <div class="col-md-6">
|
| <label class="form-label">Access Method</label>
|
| <select class="form-select" name="access_method">
|
| <option value="direct" {% if config.access_method == 'direct' %}selected{% endif %}>Direct</option>
|
| <option value="proxy" {% if config.access_method == 'proxy' %}selected{% endif %}>Proxy</option>
|
| </select>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="mb-4">
|
| <h3 class="h6 mb-3">Protocol Settings</h3>
|
| <div class="row g-3">
|
| <div class="col-md-6">
|
| <label class="form-label">Default Protocol</label>
|
| <select class="form-select" name="default_protocol">
|
| <option value="shadowsocks" {% if config.default_protocol == 'shadowsocks' %}selected{% endif %}>Shadowsocks</option>
|
| <option value="wireguard" {% if config.default_protocol == 'wireguard' %}selected{% endif %}>WireGuard</option>
|
| <option value="openvpn" {% if config.default_protocol == 'openvpn' %}selected{% endif %}>OpenVPN</option>
|
| </select>
|
| </div>
|
| <div class="col-md-6">
|
| <label class="form-label">Encryption Method</label>
|
| <select class="form-select" name="encryption_method">
|
| <option value="aes-256-gcm" {% if config.encryption_method == 'aes-256-gcm' %}selected{% endif %}>AES-256-GCM</option>
|
| <option value="chacha20-poly1305" {% if config.encryption_method == 'chacha20-poly1305' %}selected{% endif %}>ChaCha20-Poly1305</option>
|
| </select>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="mb-4">
|
| <h3 class="h6 mb-3">Security Settings</h3>
|
| <div class="row g-3">
|
| <div class="col-md-6">
|
| <label class="form-label">Session Timeout (minutes)</label>
|
| <input type="number" class="form-control" name="session_timeout" value="{{ config.session_timeout }}" required>
|
| </div>
|
| <div class="col-md-6">
|
| <label class="form-label">Max Failed Attempts</label>
|
| <input type="number" class="form-control" name="max_failed_attempts" value="{{ config.max_failed_attempts }}" required>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <div class="text-end">
|
| <button type="submit" class="btn btn-primary">Save Configuration</button>
|
| </div>
|
| </form>
|
| </div>
|
|
|
|
|
| <div class="dashboard-card mt-4">
|
| <div class="d-flex justify-content-between align-items-center mb-4">
|
| <h2 class="h5 mb-0">Audit Log</h2>
|
| <div class="btn-group">
|
| <button class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown">
|
| Export
|
| </button>
|
| <ul class="dropdown-menu">
|
| <li><a class="dropdown-item" href="{{ url_for('export_audit_log', format='csv') }}">CSV</a></li>
|
| <li><a class="dropdown-item" href="{{ url_for('export_audit_log', format='json') }}">JSON</a></li>
|
| </ul>
|
| </div>
|
| </div>
|
| <div class="table-responsive">
|
| <table class="table">
|
| <thead>
|
| <tr>
|
| <th>Timestamp</th>
|
| <th>User</th>
|
| <th>Action</th>
|
| <th>Details</th>
|
| </tr>
|
| </thead>
|
| <tbody>
|
| {% for log in audit_logs %}
|
| <tr>
|
| <td>{{ log.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
| <td>{{ log.username }}</td>
|
| <td>{{ log.action }}</td>
|
| <td>{{ log.details }}</td>
|
| </tr>
|
| {% endfor %}
|
| </tbody>
|
| </table>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
|
|
| <div class="dashboard-card mt-4">
|
| <h2 class="h5 mb-4">Active Alerts</h2>
|
| <div class="alerts-list">
|
| {% for alert in active_alerts %}
|
| <div class="alert alert-{{ alert.severity }} d-flex align-items-center">
|
| <i class="bi bi-exclamation-triangle me-2"></i>
|
| <div>
|
| <strong>{{ alert.title }}</strong>
|
| <p class="mb-0 small">{{ alert.message }}</p>
|
| <small class="text-muted">{{ alert.timestamp.strftime('%Y-%m-%d %H:%M:%S') }}</small>
|
| </div>
|
| </div>
|
| {% endfor %}
|
| {% if not active_alerts %}
|
| <p class="text-muted text-center mb-0">No active alerts</p>
|
| {% endif %}
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
| </main>
|
| </div>
|
|
|
|
|
| <div class="modal fade" id="backupModal" tabindex="-1">
|
| <div class="modal-dialog">
|
| <div class="modal-content">
|
| <div class="modal-header">
|
| <h5 class="modal-title">Backup Configuration</h5>
|
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| </div>
|
| <div class="modal-body">
|
| <p>This will create a backup of your entire server configuration, including:</p>
|
| <ul>
|
| <li>Server settings</li>
|
| <li>User configurations</li>
|
| <li>Security settings</li>
|
| <li>Protocol configurations</li>
|
| </ul>
|
| <div class="form-check mb-3">
|
| <input class="form-check-input" type="checkbox" id="includeUserData">
|
| <label class="form-check-label" for="includeUserData">
|
| Include user data and statistics
|
| </label>
|
| </div>
|
| </div>
|
| <div class="modal-footer">
|
| <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
| <button type="button" class="btn btn-primary" onclick="createBackup()">Create Backup</button>
|
| </div>
|
| </div>
|
| </div>
|
| </div>
|
|
|
|
|
| <div class="modal fade" id="restoreModal" tabindex="-1">
|
| <div class="modal-dialog">
|
| <div class="modal-content">
|
| <div class="modal-header">
|
| <h5 class="modal-title">Restore Configuration</h5>
|
| <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| </div>
|
| <form action="{{ url_for('restore_config') }}" method="POST" enctype="multipart/form-data">
|
| <div class="modal-body">
|
| <div class="mb-3">
|
| <label class="form-label">Select Backup File</label>
|
| <input type="file" class="form-control" name="backup_file" accept=".zip,.json" required>
|
| </div>
|
| <div class="alert alert-warning">
|
| <i class="bi bi-exclamation-triangle me-2"></i>
|
| Restoring a backup will overwrite your current configuration. This action cannot be undone.
|
| </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">Restore Configuration</button>
|
| </div>
|
| </form>
|
| </div>
|
| </div>
|
| </div>
|
| {% endblock %}
|
|
|
| {% block extra_js %}
|
| <script>
|
|
|
| function updateSystemHealth() {
|
| fetch('/api/system-health')
|
| .then(response => response.json())
|
| .then(data => {
|
| updateProgressBar('cpu-usage', data.cpu_usage);
|
| updateProgressBar('memory-usage', data.memory_usage);
|
| updateProgressBar('disk-usage', data.disk_usage);
|
| });
|
| }
|
|
|
| function updateProgressBar(id, value) {
|
| const bar = document.getElementById(id);
|
| const label = bar.previousElementSibling;
|
| bar.style.width = value + '%';
|
| label.textContent = value + '%';
|
|
|
| const colorClass = value < 70 ? 'success' : value < 90 ? 'warning' : 'danger';
|
| bar.className = `progress-bar bg-${colorClass}`;
|
| }
|
|
|
|
|
| function createBackup() {
|
| const includeUserData = document.getElementById('includeUserData').checked;
|
| window.location.href = `{{ url_for('create_backup') }}?include_user_data=${includeUserData}`;
|
| }
|
|
|
|
|
| setInterval(updateSystemHealth, 5000);
|
| </script>
|
| {% endblock %}
|
|
|