699 lines
24 KiB
Python
699 lines
24 KiB
Python
"""HTMX partial routes for dynamic updates."""
|
|
|
|
from fastapi import APIRouter, Request, Depends, Form
|
|
from fastapi.responses import HTMLResponse
|
|
from sqlalchemy.orm import Session
|
|
from ..database import get_db
|
|
from ..models.user import User, UserRole
|
|
from ..models.gateway import Gateway
|
|
from ..models.endpoint import Endpoint
|
|
from ..models.access import UserGatewayAccess, ConnectionLog
|
|
from ..models.vpn_server import VPNServer
|
|
from ..models.vpn_connection_log import VPNConnectionLog
|
|
from ..services.vpn_server_service import VPNServerService
|
|
from ..services.vpn_sync_service import VPNSyncService
|
|
from .deps import get_current_user_web
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/dashboard/stats", response_class=HTMLResponse)
|
|
async def dashboard_stats(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Dashboard statistics partial."""
|
|
gateways_total = db.query(Gateway).count()
|
|
gateways_online = db.query(Gateway).filter(Gateway.is_online == True).count()
|
|
endpoints_total = db.query(Endpoint).count()
|
|
users_total = db.query(User).filter(User.is_active == True).count()
|
|
active_connections = db.query(ConnectionLog).filter(
|
|
ConnectionLog.disconnected_at.is_(None)
|
|
).count()
|
|
|
|
# Count VPN clients across all active servers
|
|
vpn_clients_total = 0
|
|
vpn_servers = db.query(VPNServer).filter(VPNServer.is_active == True).all()
|
|
service = VPNServerService(db)
|
|
for server in vpn_servers:
|
|
try:
|
|
clients = service.get_connected_clients(server)
|
|
vpn_clients_total += len(clients)
|
|
except:
|
|
pass # Server might be offline
|
|
|
|
return f"""
|
|
<div class="col-md-2 mb-3">
|
|
<div class="card stat-card bg-primary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-subtitle mb-2 text-white-50">Gateways</h6>
|
|
<div class="stat-value">{gateways_online} / {gateways_total}</div>
|
|
</div>
|
|
<i class="bi bi-router stat-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<div class="card stat-card bg-success text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-subtitle mb-2 text-white-50">VPN-Clients</h6>
|
|
<div class="stat-value">{vpn_clients_total}</div>
|
|
</div>
|
|
<i class="bi bi-shield-check stat-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<div class="card stat-card bg-info text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-subtitle mb-2 text-white-50">Sessions</h6>
|
|
<div class="stat-value">{active_connections}</div>
|
|
</div>
|
|
<i class="bi bi-plug stat-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<div class="card stat-card bg-secondary text-white">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-subtitle mb-2 text-white-50">Endpunkte</h6>
|
|
<div class="stat-value">{endpoints_total}</div>
|
|
</div>
|
|
<i class="bi bi-hdd-network stat-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-2 mb-3">
|
|
<div class="card stat-card bg-warning text-dark">
|
|
<div class="card-body">
|
|
<div class="d-flex justify-content-between">
|
|
<div>
|
|
<h6 class="card-subtitle mb-2">Benutzer</h6>
|
|
<div class="stat-value">{users_total}</div>
|
|
</div>
|
|
<i class="bi bi-people stat-icon"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
|
|
@router.get("/gateways/list", response_class=HTMLResponse)
|
|
@router.get("/gateways/search", response_class=HTMLResponse)
|
|
@router.get("/gateways/filter", response_class=HTMLResponse)
|
|
async def gateway_list_partial(
|
|
request: Request,
|
|
q: str = "",
|
|
status: str = "",
|
|
type: str = "",
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Gateway list partial for HTMX."""
|
|
query = db.query(Gateway)
|
|
|
|
# Filter by tenant for non-super-admins
|
|
if current_user.role != UserRole.SUPER_ADMIN:
|
|
if current_user.role == UserRole.ADMIN:
|
|
query = query.filter(Gateway.tenant_id == current_user.tenant_id)
|
|
else:
|
|
query = query.join(
|
|
UserGatewayAccess,
|
|
UserGatewayAccess.gateway_id == Gateway.id
|
|
).filter(UserGatewayAccess.user_id == current_user.id)
|
|
|
|
# Apply filters
|
|
if q:
|
|
query = query.filter(Gateway.name.ilike(f"%{q}%"))
|
|
if status == "online":
|
|
query = query.filter(Gateway.is_online == True)
|
|
elif status == "offline":
|
|
query = query.filter(Gateway.is_online == False)
|
|
if type:
|
|
query = query.filter(Gateway.router_type == type)
|
|
|
|
gateways = query.all()
|
|
|
|
if not gateways:
|
|
return """
|
|
<div class="alert alert-info">
|
|
<i class="bi bi-info-circle"></i> Keine Gateways gefunden
|
|
</div>
|
|
"""
|
|
|
|
html = '<div class="row">'
|
|
for gw in gateways:
|
|
status_class = "online" if gw.is_online else "offline"
|
|
status_badge = '<span class="badge bg-success">Online</span>' if gw.is_online else '<span class="badge bg-secondary">Offline</span>'
|
|
last_seen = gw.last_seen.strftime('%d.%m.%Y %H:%M') if gw.last_seen else 'Nie'
|
|
|
|
html += f"""
|
|
<div class="col-md-6 col-lg-4 mb-3">
|
|
<div class="card gateway-card {status_class}">
|
|
<div class="card-body">
|
|
<h5 class="card-title">
|
|
<span class="status-indicator {status_class}"></span>
|
|
{gw.name}
|
|
</h5>
|
|
<p class="card-text text-muted small mb-2">
|
|
{gw.router_type} | {gw.location or 'Kein Standort'}
|
|
</p>
|
|
<p class="card-text small">
|
|
{status_badge}
|
|
<span class="text-muted ms-2">Zuletzt: {last_seen}</span>
|
|
</p>
|
|
<a href="/gateways/{gw.id}" class="btn btn-sm btn-outline-primary">
|
|
<i class="bi bi-eye"></i> Details
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
html += '</div>'
|
|
|
|
return html
|
|
|
|
|
|
@router.get("/gateways/status-list", response_class=HTMLResponse)
|
|
async def gateway_status_list(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Gateway status list for dashboard."""
|
|
query = db.query(Gateway)
|
|
|
|
if current_user.role != UserRole.SUPER_ADMIN:
|
|
if current_user.role == UserRole.ADMIN:
|
|
query = query.filter(Gateway.tenant_id == current_user.tenant_id)
|
|
|
|
gateways = query.limit(10).all()
|
|
|
|
if not gateways:
|
|
return '<p class="text-muted">Keine Gateways vorhanden</p>'
|
|
|
|
html = '<table class="table table-sm table-hover mb-0"><tbody>'
|
|
for gw in gateways:
|
|
status = '<span class="status-indicator online"></span>' if gw.is_online else '<span class="status-indicator offline"></span>'
|
|
html += f"""
|
|
<tr onclick="window.location='/gateways/{gw.id}'" style="cursor:pointer">
|
|
<td>{status} {gw.name}</td>
|
|
<td class="text-muted">{gw.router_type}</td>
|
|
<td class="text-end">
|
|
<a href="/gateways/{gw.id}" class="btn btn-sm btn-link">
|
|
<i class="bi bi-arrow-right"></i>
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
"""
|
|
html += '</tbody></table>'
|
|
|
|
return html
|
|
|
|
|
|
@router.get("/gateways/{gateway_id}/endpoints", response_class=HTMLResponse)
|
|
async def gateway_endpoints_partial(
|
|
request: Request,
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Endpoints list partial."""
|
|
endpoints = db.query(Endpoint).filter(Endpoint.gateway_id == gateway_id).all()
|
|
|
|
if not endpoints:
|
|
return """
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-inbox" style="font-size: 2rem;"></i>
|
|
<p>Keine Endpunkte definiert</p>
|
|
</div>
|
|
"""
|
|
|
|
html = '<table class="table table-hover mb-0"><thead><tr><th>Name</th><th>Adresse</th><th>Protokoll</th><th>Anwendung</th><th></th></tr></thead><tbody>'
|
|
|
|
for ep in endpoints:
|
|
protocol_badge = f'<span class="badge badge-{ep.protocol.value}">{ep.protocol.value.upper()}</span>'
|
|
html += f"""
|
|
<tr>
|
|
<td><strong>{ep.name}</strong></td>
|
|
<td><code>{ep.internal_ip}:{ep.port}</code></td>
|
|
<td>{protocol_badge}</td>
|
|
<td>{ep.application_name or '-'}</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-danger"
|
|
hx-delete="/htmx/endpoints/{ep.id}"
|
|
hx-confirm="Endpunkt '{ep.name}' löschen?"
|
|
hx-target="#endpoints-list"
|
|
hx-swap="innerHTML">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
"""
|
|
html += '</tbody></table>'
|
|
|
|
return html
|
|
|
|
|
|
@router.post("/gateways/{gateway_id}/endpoints", response_class=HTMLResponse)
|
|
async def create_endpoint_htmx(
|
|
request: Request,
|
|
gateway_id: int,
|
|
name: str = Form(...),
|
|
internal_ip: str = Form(...),
|
|
port: int = Form(...),
|
|
protocol: str = Form("tcp"),
|
|
application_template_id: int = Form(None),
|
|
description: str = Form(None),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Create endpoint via HTMX."""
|
|
from ..models.endpoint import Protocol
|
|
|
|
endpoint = Endpoint(
|
|
gateway_id=gateway_id,
|
|
name=name,
|
|
internal_ip=internal_ip,
|
|
port=port,
|
|
protocol=Protocol(protocol),
|
|
application_template_id=application_template_id if application_template_id else None,
|
|
description=description
|
|
)
|
|
|
|
db.add(endpoint)
|
|
db.commit()
|
|
|
|
# Return updated list
|
|
return await gateway_endpoints_partial(request, gateway_id, db, current_user)
|
|
|
|
|
|
@router.delete("/endpoints/{endpoint_id}", response_class=HTMLResponse)
|
|
async def delete_endpoint_htmx(
|
|
request: Request,
|
|
endpoint_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Delete endpoint via HTMX."""
|
|
endpoint = db.query(Endpoint).filter(Endpoint.id == endpoint_id).first()
|
|
if endpoint:
|
|
gateway_id = endpoint.gateway_id
|
|
db.delete(endpoint)
|
|
db.commit()
|
|
return await gateway_endpoints_partial(request, gateway_id, db, current_user)
|
|
return ""
|
|
|
|
|
|
@router.get("/gateways/{gateway_id}/access", response_class=HTMLResponse)
|
|
async def gateway_access_partial(
|
|
request: Request,
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""User access list partial for gateway."""
|
|
access_list = db.query(UserGatewayAccess).filter(
|
|
UserGatewayAccess.gateway_id == gateway_id
|
|
).all()
|
|
|
|
if not access_list:
|
|
return """
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-people" style="font-size: 2rem;"></i>
|
|
<p>Keine Benutzer haben Zugriff</p>
|
|
</div>
|
|
"""
|
|
|
|
html = '<table class="table table-hover mb-0"><thead><tr><th>Benutzer</th><th>Rolle</th><th>Gewährt am</th><th>Gewährt von</th><th></th></tr></thead><tbody>'
|
|
|
|
for access in access_list:
|
|
user = db.query(User).filter(User.id == access.user_id).first()
|
|
granted_by = db.query(User).filter(User.id == access.granted_by_id).first() if access.granted_by_id else None
|
|
granted_at = access.granted_at.strftime('%d.%m.%Y') if access.granted_at else '-'
|
|
|
|
html += f"""
|
|
<tr>
|
|
<td><strong>{user.username if user else '-'}</strong></td>
|
|
<td><span class="badge bg-secondary">{user.role.value if user else '-'}</span></td>
|
|
<td>{granted_at}</td>
|
|
<td>{granted_by.username if granted_by else '-'}</td>
|
|
<td class="text-end">
|
|
<button class="btn btn-sm btn-outline-danger"
|
|
hx-delete="/htmx/gateways/{gateway_id}/access/{access.user_id}"
|
|
hx-confirm="Zugriff entziehen?"
|
|
hx-target="#access-list"
|
|
hx-swap="innerHTML">
|
|
<i class="bi bi-x-lg"></i>
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
"""
|
|
html += '</tbody></table>'
|
|
|
|
return html
|
|
|
|
|
|
@router.post("/gateways/{gateway_id}/access", response_class=HTMLResponse)
|
|
async def grant_access_htmx(
|
|
request: Request,
|
|
gateway_id: int,
|
|
user_id: int = Form(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Grant user access to gateway via HTMX."""
|
|
# Check if access already exists
|
|
existing = db.query(UserGatewayAccess).filter(
|
|
UserGatewayAccess.gateway_id == gateway_id,
|
|
UserGatewayAccess.user_id == user_id
|
|
).first()
|
|
|
|
if not existing:
|
|
access = UserGatewayAccess(
|
|
gateway_id=gateway_id,
|
|
user_id=user_id,
|
|
granted_by_id=current_user.id
|
|
)
|
|
db.add(access)
|
|
db.commit()
|
|
|
|
return await gateway_access_partial(request, gateway_id, db, current_user)
|
|
|
|
|
|
@router.delete("/gateways/{gateway_id}/access/{user_id}", response_class=HTMLResponse)
|
|
async def revoke_access_htmx(
|
|
request: Request,
|
|
gateway_id: int,
|
|
user_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Revoke user access to gateway via HTMX."""
|
|
access = db.query(UserGatewayAccess).filter(
|
|
UserGatewayAccess.gateway_id == gateway_id,
|
|
UserGatewayAccess.user_id == user_id
|
|
).first()
|
|
|
|
if access:
|
|
db.delete(access)
|
|
db.commit()
|
|
|
|
return await gateway_access_partial(request, gateway_id, db, current_user)
|
|
|
|
|
|
@router.get("/gateways/{gateway_id}/vpn-log", response_class=HTMLResponse)
|
|
async def gateway_vpn_log_partial(
|
|
request: Request,
|
|
gateway_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""VPN connection log for gateway."""
|
|
sync_service = VPNSyncService(db)
|
|
logs = sync_service.get_gateway_connection_logs(gateway_id, limit=20)
|
|
|
|
if not logs:
|
|
return """
|
|
<div class="text-center text-muted py-4">
|
|
<i class="bi bi-shield-x" style="font-size: 2rem;"></i>
|
|
<p>Keine VPN-Verbindungen aufgezeichnet</p>
|
|
</div>
|
|
"""
|
|
|
|
html = '''<table class="table table-hover table-sm mb-0">
|
|
<thead>
|
|
<tr>
|
|
<th>Profil</th>
|
|
<th>Server</th>
|
|
<th>Echte Adresse</th>
|
|
<th>Verbunden</th>
|
|
<th>Getrennt</th>
|
|
<th>Dauer</th>
|
|
<th>Traffic</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>'''
|
|
|
|
for log in logs:
|
|
profile_name = log.vpn_profile.name if log.vpn_profile else '-'
|
|
server_name = log.vpn_server.name if log.vpn_server else '-'
|
|
real_addr = log.real_address or '-'
|
|
connected = log.connected_at.strftime('%d.%m. %H:%M') if log.connected_at else '-'
|
|
|
|
if log.disconnected_at:
|
|
disconnected = log.disconnected_at.strftime('%H:%M')
|
|
duration = log.duration_seconds or 0
|
|
if duration >= 3600:
|
|
duration_str = f"{duration // 3600}h {(duration % 3600) // 60}m"
|
|
elif duration >= 60:
|
|
duration_str = f"{duration // 60}m"
|
|
else:
|
|
duration_str = f"{duration}s"
|
|
status_badge = ''
|
|
else:
|
|
disconnected = '<span class="badge bg-success">Aktiv</span>'
|
|
duration_str = '-'
|
|
status_badge = ''
|
|
|
|
rx = log.bytes_received or 0
|
|
tx = log.bytes_sent or 0
|
|
rx_str = f"{rx / 1024 / 1024:.1f}" if rx > 1024*1024 else f"{rx / 1024:.0f}K"
|
|
tx_str = f"{tx / 1024 / 1024:.1f}" if tx > 1024*1024 else f"{tx / 1024:.0f}K"
|
|
traffic = f"↓{rx_str} ↑{tx_str}"
|
|
|
|
html += f'''
|
|
<tr>
|
|
<td>{profile_name}</td>
|
|
<td>{server_name}</td>
|
|
<td><code class="small">{real_addr}</code></td>
|
|
<td>{connected}</td>
|
|
<td>{disconnected}</td>
|
|
<td>{duration_str}</td>
|
|
<td class="small">{traffic}</td>
|
|
</tr>'''
|
|
|
|
html += '</tbody></table>'
|
|
return html
|
|
|
|
|
|
@router.get("/connections/count", response_class=HTMLResponse)
|
|
async def connections_count(
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Active connections count."""
|
|
count = db.query(ConnectionLog).filter(
|
|
ConnectionLog.disconnected_at.is_(None)
|
|
).count()
|
|
|
|
# Also count VPN clients
|
|
vpn_clients = 0
|
|
vpn_servers = db.query(VPNServer).filter(VPNServer.is_active == True).all()
|
|
service = VPNServerService(db)
|
|
for server in vpn_servers:
|
|
try:
|
|
clients = service.get_connected_clients(server)
|
|
vpn_clients += len(clients)
|
|
except:
|
|
pass
|
|
|
|
return f'<i class="bi bi-shield-check"></i> {vpn_clients} VPN-Clients <i class="bi bi-plug"></i> {count} Sessions'
|
|
|
|
|
|
@router.get("/connections/vpn-clients", response_class=HTMLResponse)
|
|
async def vpn_clients_list(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""List of connected VPN clients (gateways)."""
|
|
# Sync connections with database (updates gateway status, creates logs)
|
|
sync_service = VPNSyncService(db)
|
|
sync_service.sync_all_connections()
|
|
|
|
# Get active connections from database
|
|
active_connections = sync_service.get_active_connections()
|
|
|
|
if not active_connections:
|
|
return '<p class="text-muted text-center py-3">Keine VPN-Clients verbunden</p>'
|
|
|
|
html = '''<table class="table table-hover table-sm">
|
|
<thead>
|
|
<tr>
|
|
<th>Gateway</th>
|
|
<th>Profil</th>
|
|
<th>VPN-Server</th>
|
|
<th>Echte Adresse</th>
|
|
<th>Empfangen</th>
|
|
<th>Gesendet</th>
|
|
<th>Verbunden seit</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>'''
|
|
|
|
for conn in active_connections:
|
|
gateway_name = conn.gateway.name if conn.gateway else '-'
|
|
gateway_id = conn.gateway.id if conn.gateway else ''
|
|
profile_name = conn.vpn_profile.name if conn.vpn_profile else '-'
|
|
server_name = conn.vpn_server.name if conn.vpn_server else '-'
|
|
server_id = conn.vpn_server.id if conn.vpn_server else ''
|
|
real_addr = conn.real_address or '-'
|
|
rx = conn.bytes_received or 0
|
|
tx = conn.bytes_sent or 0
|
|
connected = conn.connected_at.strftime('%d.%m.%Y %H:%M') if conn.connected_at else '-'
|
|
|
|
# Format bytes
|
|
rx_str = f"{rx / 1024 / 1024:.2f} MB" if rx > 1024*1024 else f"{rx / 1024:.1f} KB"
|
|
tx_str = f"{tx / 1024 / 1024:.2f} MB" if tx > 1024*1024 else f"{tx / 1024:.1f} KB"
|
|
|
|
html += f'''
|
|
<tr>
|
|
<td><a href="/gateways/{gateway_id}"><strong>{gateway_name}</strong></a></td>
|
|
<td>{profile_name}</td>
|
|
<td><a href="/vpn-servers/{server_id}">{server_name}</a></td>
|
|
<td><code>{real_addr}</code></td>
|
|
<td>{rx_str}</td>
|
|
<td>{tx_str}</td>
|
|
<td>{connected}</td>
|
|
</tr>'''
|
|
|
|
html += '</tbody></table>'
|
|
return html
|
|
|
|
|
|
@router.get("/connections/active", response_class=HTMLResponse)
|
|
async def active_connections(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Active connections partial."""
|
|
connections = db.query(ConnectionLog).filter(
|
|
ConnectionLog.disconnected_at.is_(None)
|
|
).all()
|
|
|
|
if not connections:
|
|
return '<p class="text-muted text-center py-3">Keine aktiven Verbindungen</p>'
|
|
|
|
html = '<ul class="list-group list-group-flush">'
|
|
for conn in connections:
|
|
user = db.query(User).filter(User.id == conn.user_id).first()
|
|
gateway = db.query(Gateway).filter(Gateway.id == conn.gateway_id).first()
|
|
endpoint = db.query(Endpoint).filter(Endpoint.id == conn.endpoint_id).first() if conn.endpoint_id else None
|
|
|
|
html += f"""
|
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
|
<div>
|
|
<strong>{user.username if user else 'Unknown'}</strong>
|
|
<i class="bi bi-arrow-right mx-2"></i>
|
|
{gateway.name if gateway else 'Unknown'}
|
|
{f' / {endpoint.name}' if endpoint else ''}
|
|
</div>
|
|
<span class="badge bg-success"><i class="bi bi-broadcast"></i> Verbunden</span>
|
|
</li>
|
|
"""
|
|
html += '</ul>'
|
|
|
|
return html
|
|
|
|
|
|
@router.get("/connections/recent", response_class=HTMLResponse)
|
|
async def recent_connections(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Recent connections for dashboard."""
|
|
connections = db.query(ConnectionLog).order_by(
|
|
ConnectionLog.connected_at.desc()
|
|
).limit(5).all()
|
|
|
|
if not connections:
|
|
return '<p class="text-muted text-center">Keine Verbindungen</p>'
|
|
|
|
html = '<ul class="list-group list-group-flush">'
|
|
for conn in connections:
|
|
user = db.query(User).filter(User.id == conn.user_id).first()
|
|
gateway = db.query(Gateway).filter(Gateway.id == conn.gateway_id).first()
|
|
time = conn.connected_at.strftime('%H:%M')
|
|
|
|
if conn.disconnected_at:
|
|
status = '<span class="text-muted"><i class="bi bi-x-circle"></i></span>'
|
|
else:
|
|
status = '<span class="text-success"><i class="bi bi-check-circle"></i></span>'
|
|
|
|
html += f"""
|
|
<li class="list-group-item d-flex justify-content-between align-items-center py-2">
|
|
<span>
|
|
{status}
|
|
<span class="ms-2">{user.username if user else '?'}</span>
|
|
<i class="bi bi-arrow-right text-muted mx-1"></i>
|
|
<span class="text-muted">{gateway.name if gateway else '?'}</span>
|
|
</span>
|
|
<small class="text-muted">{time}</small>
|
|
</li>
|
|
"""
|
|
html += '</ul>'
|
|
|
|
return html
|
|
|
|
|
|
@router.get("/connections/list", response_class=HTMLResponse)
|
|
async def connections_list(
|
|
request: Request,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user_web)
|
|
):
|
|
"""Connection history list."""
|
|
connections = db.query(ConnectionLog).order_by(
|
|
ConnectionLog.connected_at.desc()
|
|
).limit(50).all()
|
|
|
|
if not connections:
|
|
return '<p class="text-muted text-center py-4">Keine Verbindungshistorie</p>'
|
|
|
|
html = '<table class="table table-hover"><thead><tr><th>Benutzer</th><th>Gateway</th><th>Endpunkt</th><th>Verbunden</th><th>Getrennt</th><th>Dauer</th></tr></thead><tbody>'
|
|
|
|
for conn in connections:
|
|
user = db.query(User).filter(User.id == conn.user_id).first()
|
|
gateway = db.query(Gateway).filter(Gateway.id == conn.gateway_id).first()
|
|
endpoint = db.query(Endpoint).filter(Endpoint.id == conn.endpoint_id).first() if conn.endpoint_id else None
|
|
|
|
connected = conn.connected_at.strftime('%d.%m.%Y %H:%M')
|
|
disconnected = conn.disconnected_at.strftime('%H:%M') if conn.disconnected_at else '<span class="badge bg-success">Aktiv</span>'
|
|
|
|
duration = ""
|
|
if conn.duration_seconds:
|
|
mins = conn.duration_seconds // 60
|
|
duration = f"{mins} Min."
|
|
|
|
html += f"""
|
|
<tr>
|
|
<td>{user.username if user else '-'}</td>
|
|
<td>{gateway.name if gateway else '-'}</td>
|
|
<td>{endpoint.name if endpoint else '-'}</td>
|
|
<td>{connected}</td>
|
|
<td>{disconnected}</td>
|
|
<td>{duration}</td>
|
|
</tr>
|
|
"""
|
|
|
|
html += '</tbody></table>'
|
|
return html
|
|
|
|
|