Linux agent functional. Added new client-facing server panel. Removed deferred pydantic annotations.
This commit is contained in:
@@ -59,12 +59,11 @@ class AgentCertificateAuthorityAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "is_active", "created_at", "revoked_at")
|
||||
list_filter = ("is_active", "created_at", "revoked_at")
|
||||
search_fields = ("name", "fingerprint")
|
||||
readonly_fields = ("fingerprint", "serial", "created_at", "revoked_at", "created_by")
|
||||
readonly_fields = ("cert_pem", "fingerprint", "serial", "created_at", "revoked_at", "created_by")
|
||||
fields = (
|
||||
"name",
|
||||
"is_active",
|
||||
"cert_pem",
|
||||
"key_pem",
|
||||
"fingerprint",
|
||||
"serial",
|
||||
"created_by",
|
||||
|
||||
67
app/apps/servers/templates/servers/dashboard.html
Normal file
67
app/apps/servers/templates/servers/dashboard.html
Normal file
@@ -0,0 +1,67 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Servers • Keywarden{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-8">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">Servers</h1>
|
||||
<p class="mt-2 text-sm text-gray-600">Your active server access and recent activity at a glance.</p>
|
||||
</div>
|
||||
|
||||
{% if servers %}
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{% for item in servers %}
|
||||
<article class="group relative overflow-hidden rounded-2xl border border-gray-200 bg-white p-5 shadow-sm transition hover:-translate-y-0.5 hover:shadow-md">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-600 text-white font-semibold">
|
||||
{{ item.server.initial }}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold text-gray-900">{{ item.server.display_name }}</h2>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ item.server.hostname|default:item.server.ipv4|default:item.server.ipv6|default:"Unassigned" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="inline-flex items-center rounded-full bg-emerald-50 px-2.5 py-1 text-xs font-semibold text-emerald-700">Active</span>
|
||||
</div>
|
||||
|
||||
<dl class="mt-4 space-y-2 text-sm text-gray-600">
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>Access until</dt>
|
||||
<dd class="font-medium text-gray-900">
|
||||
{% if item.expires_at %}
|
||||
{{ item.expires_at|date:"M j, Y H:i" }}
|
||||
{% else %}
|
||||
No expiry
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>Last accessed</dt>
|
||||
<dd class="font-medium text-gray-900">
|
||||
{% if item.last_accessed %}
|
||||
{{ item.last_accessed|date:"M j, Y H:i" }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
<div class="mt-4 border-t border-gray-100 pt-3 text-xs text-gray-500">
|
||||
<a href="{% url 'servers:detail' item.server.id %}" class="font-semibold text-purple-700 hover:text-purple-800">View details and logs</a>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="rounded-xl border border-dashed border-gray-300 bg-white p-10 text-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900">No server access yet</h2>
|
||||
<p class="mt-2 text-sm text-gray-600">Request access to a server to see it here.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
78
app/apps/servers/templates/servers/detail.html
Normal file
78
app/apps/servers/templates/servers/detail.html
Normal file
@@ -0,0 +1,78 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ server.display_name }} • Keywarden{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="space-y-8">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="flex items-center gap-4">
|
||||
<div class="flex h-12 w-12 items-center justify-center rounded-xl bg-purple-600 text-white text-xl font-semibold">
|
||||
{{ server.initial }}
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-gray-900">{{ server.display_name }}</h1>
|
||||
<p class="text-sm text-gray-600">
|
||||
{{ server.hostname|default:server.ipv4|default:server.ipv6|default:"Unassigned" }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{% url 'servers:dashboard' %}" class="text-sm font-semibold text-purple-700 hover:text-purple-800">Back to servers</a>
|
||||
</div>
|
||||
|
||||
<section class="grid gap-4 lg:grid-cols-3">
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm lg:col-span-2">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Access</h2>
|
||||
<dl class="mt-4 space-y-3 text-sm text-gray-600">
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>Access until</dt>
|
||||
<dd class="font-medium text-gray-900">
|
||||
{% if expires_at %}
|
||||
{{ expires_at|date:"M j, Y H:i" }}
|
||||
{% else %}
|
||||
No expiry
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>Last accessed</dt>
|
||||
<dd class="font-medium text-gray-900">
|
||||
{% if last_accessed %}
|
||||
{{ last_accessed|date:"M j, Y H:i" }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Server details</h2>
|
||||
<dl class="mt-4 space-y-3 text-sm text-gray-600">
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>Hostname</dt>
|
||||
<dd class="font-medium text-gray-900">{{ server.hostname|default:"—" }}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>IPv4</dt>
|
||||
<dd class="font-medium text-gray-900">{{ server.ipv4|default:"—" }}</dd>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<dt>IPv6</dt>
|
||||
<dd class="font-medium text-gray-900">{{ server.ipv6|default:"—" }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="rounded-2xl border border-gray-200 bg-white p-5 shadow-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-gray-900">Logs</h2>
|
||||
<span class="text-xs font-semibold text-gray-500">Placeholder</span>
|
||||
</div>
|
||||
<div class="mt-4 rounded-xl border border-dashed border-gray-200 bg-gray-50 p-6 text-center text-sm text-gray-600">
|
||||
Logs will appear here once collection is enabled for this server.
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
{% endblock %}
|
||||
10
app/apps/servers/urls.py
Normal file
10
app/apps/servers/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = "servers"
|
||||
|
||||
urlpatterns = [
|
||||
path("", views.dashboard, name="dashboard"),
|
||||
path("<int:server_id>/", views.detail, name="detail"),
|
||||
]
|
||||
67
app/apps/servers/views.py
Normal file
67
app/apps/servers/views.py
Normal file
@@ -0,0 +1,67 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Q
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.access.models import AccessRequest
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
def dashboard(request):
|
||||
now = timezone.now()
|
||||
access_qs = (
|
||||
AccessRequest.objects.select_related("server")
|
||||
.filter(
|
||||
requester=request.user,
|
||||
status=AccessRequest.Status.APPROVED,
|
||||
)
|
||||
.filter(Q(expires_at__isnull=True) | Q(expires_at__gt=now))
|
||||
.order_by("-requested_at")
|
||||
)
|
||||
|
||||
seen = set()
|
||||
servers = []
|
||||
for access in access_qs:
|
||||
if access.server_id in seen:
|
||||
continue
|
||||
seen.add(access.server_id)
|
||||
servers.append(
|
||||
{
|
||||
"server": access.server,
|
||||
"expires_at": access.expires_at,
|
||||
"last_accessed": None,
|
||||
}
|
||||
)
|
||||
|
||||
context = {
|
||||
"servers": servers,
|
||||
}
|
||||
return render(request, "servers/dashboard.html", context)
|
||||
|
||||
|
||||
@login_required(login_url="/accounts/login/")
|
||||
def detail(request, server_id: int):
|
||||
now = timezone.now()
|
||||
access = (
|
||||
AccessRequest.objects.select_related("server")
|
||||
.filter(
|
||||
requester=request.user,
|
||||
server_id=server_id,
|
||||
status=AccessRequest.Status.APPROVED,
|
||||
)
|
||||
.filter(Q(expires_at__isnull=True) | Q(expires_at__gt=now))
|
||||
.order_by("-requested_at")
|
||||
.first()
|
||||
)
|
||||
if not access:
|
||||
raise Http404("Server not found")
|
||||
|
||||
context = {
|
||||
"server": access.server,
|
||||
"expires_at": access.expires_at,
|
||||
"last_accessed": None,
|
||||
}
|
||||
return render(request, "servers/detail.html", context)
|
||||
Reference in New Issue
Block a user