openvpn-endpoint-server/server/app/templates/vpn_servers/detail.html

261 lines
10 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ server.name }} - VPN-Server - mGuard VPN{% endblock %}
{% block content %}
<nav aria-label="breadcrumb" class="mb-3">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/vpn-servers">VPN-Server</a></li>
<li class="breadcrumb-item active">{{ server.name }}</li>
</ol>
</nav>
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>
{% if server.status.value == 'running' %}
<span class="status-indicator online"></span>
{% else %}
<span class="status-indicator offline"></span>
{% endif %}
{{ server.name }}
{% if server.is_primary %}
<span class="badge bg-primary">Primär</span>
{% endif %}
{% if server.protocol.value == 'udp' %}
<span class="badge badge-udp">UDP</span>
{% else %}
<span class="badge badge-tcp">TCP</span>
{% endif %}
</h1>
<div>
<a href="/vpn-servers/{{ server.id }}/clients" class="btn btn-outline-primary">
<i class="bi bi-people"></i> Clients ({{ status.connected_clients or 0 }})
</a>
<a href="/vpn-servers/{{ server.id }}/edit" class="btn btn-outline-secondary">
<i class="bi bi-pencil"></i> Bearbeiten
</a>
<form action="/vpn-servers/{{ server.id }}/delete" method="post" class="d-inline"
onsubmit="return confirm('VPN-Server wirklich löschen?');">
<button type="submit" class="btn btn-outline-danger">
<i class="bi bi-trash"></i> Löschen
</button>
</form>
</div>
</div>
<div class="row">
<!-- Status Card -->
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-activity"></i> Status</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5">Status</dt>
<dd class="col-sm-7">
{% if server.status.value == 'running' %}
<span class="badge bg-success">Läuft</span>
{% elif server.status.value == 'stopped' %}
<span class="badge bg-secondary">Gestoppt</span>
{% elif server.status.value == 'starting' %}
<span class="badge bg-warning">Startet...</span>
{% elif server.status.value == 'error' %}
<span class="badge bg-danger">Fehler</span>
{% else %}
<span class="badge bg-light text-dark">{{ server.status.value }}</span>
{% endif %}
</dd>
<dt class="col-sm-5">Verbundene Clients</dt>
<dd class="col-sm-7">{{ status.connected_clients or 0 }}</dd>
<dt class="col-sm-5">Letzte Prüfung</dt>
<dd class="col-sm-7">
{{ server.last_status_check.strftime('%d.%m.%Y %H:%M') if server.last_status_check else '-' }}
</dd>
<dt class="col-sm-5">Container</dt>
<dd class="col-sm-7"><code>{{ server.docker_container_name or '-' }}</code></dd>
<dt class="col-sm-5">Management-Port</dt>
<dd class="col-sm-7">{{ server.management_port }}</dd>
</dl>
</div>
</div>
</div>
<!-- Network Card -->
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-hdd-network"></i> Netzwerk</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5">Adresse</dt>
<dd class="col-sm-7"><code>{{ server.hostname }}:{{ server.port }}</code></dd>
<dt class="col-sm-5">Protokoll</dt>
<dd class="col-sm-7">{{ server.protocol.value.upper() }}</dd>
<dt class="col-sm-5">VPN-Netzwerk</dt>
<dd class="col-sm-7">{{ server.vpn_network }}/{{ server.vpn_netmask }}</dd>
<dt class="col-sm-5">Max Clients</dt>
<dd class="col-sm-7">{{ server.max_clients }}</dd>
<dt class="col-sm-5">Keepalive</dt>
<dd class="col-sm-7">{{ server.keepalive_interval }}s / {{ server.keepalive_timeout }}s</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="row">
<!-- Security Card -->
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-shield-lock"></i> Sicherheit</h5>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-5">CA</dt>
<dd class="col-sm-7">
<a href="/ca/{{ server.certificate_authority.id }}">
{{ server.certificate_authority.name }}
</a>
</dd>
<dt class="col-sm-5">Cipher</dt>
<dd class="col-sm-7">{{ server.cipher.value }}</dd>
<dt class="col-sm-5">Auth</dt>
<dd class="col-sm-7">{{ server.auth.value }}</dd>
<dt class="col-sm-5">TLS Version</dt>
<dd class="col-sm-7">>= {{ server.tls_version_min }}</dd>
<dt class="col-sm-5">TLS-Auth</dt>
<dd class="col-sm-7">
{% if server.tls_auth_enabled %}
<span class="badge bg-success">Aktiviert</span>
{% else %}
<span class="badge bg-secondary">Deaktiviert</span>
{% endif %}
</dd>
<dt class="col-sm-5">Kompression</dt>
<dd class="col-sm-7">{{ server.compression.value }}</dd>
</dl>
</div>
</div>
</div>
<!-- Profiles Card -->
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-person-vcard"></i> VPN-Profile</h5>
</div>
<div class="card-body">
{% if server.vpn_profiles %}
<ul class="list-group list-group-flush">
{% for profile in server.vpn_profiles[:10] %}
<li class="list-group-item d-flex justify-content-between align-items-center px-0">
<span>
<a href="/gateways/{{ profile.gateway_id }}/profiles/{{ profile.id }}">
{{ profile.gateway.name }} - {{ profile.name }}
</a>
</span>
{% if profile.status.value == 'active' %}
<span class="badge bg-success">Aktiv</span>
{% elif profile.status.value == 'provisioned' %}
<span class="badge bg-info">Provisioniert</span>
{% else %}
<span class="badge bg-secondary">{{ profile.status.value }}</span>
{% endif %}
</li>
{% endfor %}
</ul>
{% if server.vpn_profiles|length > 10 %}
<p class="text-muted small mt-2 mb-0">... und {{ server.vpn_profiles|length - 10 }} weitere</p>
{% endif %}
{% else %}
<p class="text-muted mb-0">Keine Profile verwenden diesen Server.</p>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Server Log -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0"><i class="bi bi-terminal"></i> Server-Log</h5>
<div>
<button class="btn btn-sm btn-outline-secondary" onclick="refreshLog()">
<i class="bi bi-arrow-clockwise"></i> Aktualisieren
</button>
<a href="/api/internal/vpn-servers/{{ server.id }}/logs/raw?lines=500"
class="btn btn-sm btn-outline-primary" target="_blank">
<i class="bi bi-download"></i> Download
</a>
</div>
</div>
<div class="card-body p-0">
<div id="server-log" class="bg-dark text-light p-3"
style="max-height: 400px; overflow-y: auto; font-family: monospace; font-size: 12px;">
<div class="text-center py-4">
<div class="spinner-border spinner-border-sm text-light" role="status">
<span class="visually-hidden">Laden...</span>
</div>
<span class="ms-2">Log wird geladen...</span>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
async function refreshLog() {
const container = document.getElementById('server-log');
try {
const response = await fetch('/api/internal/vpn-servers/{{ server.id }}/logs?lines=100');
const data = await response.json();
if (data.lines && data.lines.length > 0) {
container.innerHTML = data.lines.map(line => {
// Color-code log levels
let lineClass = '';
if (line.includes('ERROR') || line.includes('error')) lineClass = 'text-danger';
else if (line.includes('WARN') || line.includes('warning')) lineClass = 'text-warning';
else if (line.includes('INFO')) lineClass = 'text-info';
return `<div class="${lineClass}">${escapeHtml(line)}</div>`;
}).join('');
container.scrollTop = container.scrollHeight;
} else {
container.innerHTML = '<div class="text-muted">Keine Log-Einträge vorhanden</div>';
}
} catch (error) {
container.innerHTML = `<div class="text-danger">Fehler beim Laden: ${error.message}</div>`;
}
}
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// Load log on page load
document.addEventListener('DOMContentLoaded', refreshLog);
// Auto-refresh every 10 seconds
setInterval(refreshLog, 10000);
</script>
{% endblock %}