diff --git a/app/apps/accounts/forms.py b/app/apps/accounts/forms.py index 16faf07..d048bd7 100644 --- a/app/apps/accounts/forms.py +++ b/app/apps/accounts/forms.py @@ -8,7 +8,7 @@ class ErasureRequestForm(forms.Form): attrs={ "rows": 4, "placeholder": "Explain why you are requesting data erasure.", - "class": "w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-purple-600 focus:outline-none focus:ring-1 focus:ring-purple-600", + "class": "block w-full resize-y rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500", } ), min_length=10, @@ -18,22 +18,22 @@ class ErasureRequestForm(forms.Form): class SSHKeyForm(forms.Form): name = forms.CharField( - label="Key name", + label="Key Name", max_length=128, widget=forms.TextInput( attrs={ - "placeholder": "MacBook Pro", - "class": "w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-purple-600 focus:outline-none focus:ring-1 focus:ring-purple-600", + "placeholder": "Device Name", + "class": "block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500", } ), ) public_key = forms.CharField( - label="SSH public key", + label="SSH Public Key", widget=forms.Textarea( attrs={ "rows": 4, - "placeholder": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB... you@host", - "class": "w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 shadow-sm focus:border-purple-600 focus:outline-none focus:ring-1 focus:ring-purple-600", + "placeholder": "ssh-ed25519 AAAaBBbBcCcc111122223333... user@host", + "class": "block w-full resize-y rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500", } ), ) diff --git a/app/apps/accounts/templates/accounts/login.html b/app/apps/accounts/templates/accounts/login.html index 3310642..1951275 100644 --- a/app/apps/accounts/templates/accounts/login.html +++ b/app/apps/accounts/templates/accounts/login.html @@ -4,32 +4,53 @@ {% block content %}
-
-

Sign in

-
+
+
+

Welcome back

+

Sign in to manage server access and certificates.

+
+ + {% csrf_token %} -
- - +
+ +
-
- - +
+ +
{% if form.errors %} -

Please check your username and password.

+ {% endif %} -
- -
+ +

Or, if configured, use - OIDC login. + OIDC login.

diff --git a/app/apps/accounts/templates/accounts/profile.html b/app/apps/accounts/templates/accounts/profile.html index 90a693f..6016655 100644 --- a/app/apps/accounts/templates/accounts/profile.html +++ b/app/apps/accounts/templates/accounts/profile.html @@ -3,136 +3,225 @@ {% block title %}Profile • Keywarden{% endblock %} {% block content %} -
-
-
-

Your Profile

-
-
-
Username
-
{{ user.username }}
+
+
+
+
+

Your Profile

+

Account details and contact information.

+
+
+
+
Username
+
{{ user.username }}
-
-
Email
-
{{ user.email }}
+
+
Email
+
{{ user.email }}
-
-
First name
-
{{ user.first_name|default:"—" }}
+
+
First name
+
{{ user.first_name|default:"—" }}
-
-
Last name
-
{{ user.last_name|default:"—" }}
+
+
Last name
+
{{ user.last_name|default:"—" }}
-
-
-
-
-

Single Sign-On

- {% if auth_mode == "hybrid" %} -
-

- Optional: Link your account with your identity provider for single sign-on. - Link with SSO -

-
- {% elif auth_mode == "oidc" %} -

OIDC is required. Sign-in is managed by your identity provider.

- {% else %} -

OIDC is disabled. You are using native authentication.

- {% endif %} -
-
-
+ -
-

SSH certificates

-

- Upload your SSH public key to receive a signed certificate for server access. -

+
+
+

Single Sign-On

+

Manage how you authenticate with external providers.

+
+
+ {% if auth_mode == "hybrid" %} + Optional: Link your account with your identity provider for single sign-on. + Link with SSO + {% elif auth_mode == "oidc" %} + OIDC is required. Sign-in is managed by your identity provider. + {% else %} + OIDC is disabled. You are using native authentication. + {% endif %} +
+
+
- {% if can_add_key %} -
- {% csrf_token %} - +
+
- -
+

SSH certificates

+

+ Upload your SSH public key to receive a signed certificate for server access. +

+
+ Certificates +
+ + {% if can_add_key %} + + {% csrf_token %} + +
+ {{ key_form.name }} + {% if key_form.name.errors %} +

{{ key_form.name.errors|striptags }}

+ {% endif %}
- {% if key_form.name.errors %} -

{{ key_form.name.errors|striptags }}

- {% endif %} -
-
- -
+
+ {{ key_form.public_key }} + {% if key_form.public_key.errors %} +

{{ key_form.public_key.errors|striptags }}

+ {% endif %}
- {% if key_form.public_key.errors %} -

{{ key_form.public_key.errors|striptags }}

+ {% if key_form.non_field_errors %} +

{{ key_form.non_field_errors|striptags }}

+ {% endif %} +
+ +
+ + {% else %} +

You do not have permission to add SSH keys.

+ {% endif %} + + {% if ssh_keys %} +
+ + + + + + + + + + {% for key in ssh_keys %} + + + + + + {% endfor %} + +
KeyFingerprintActions
+ {{ key.name }} + {{ key.fingerprint }} +
+ {% if key.is_active %} + Active +
+ + +
+ + {% else %} + Revoked + {% endif %} +
+
+
+ {% else %} +

No SSH keys uploaded yet.

+ {% endif %} +
+ +
+
+
+

Data erasure request

+

+ Submit a GDPR erasure request to anonymize your account data. An administrator + must review and approve the request before processing. +

+
+ GDPR +
+ + {% if erasure_request %} +
+
+ Status + + {{ erasure_request.status|capfirst }} + + Requested {{ erasure_request.requested_at|date:"M j, Y H:i" }} +
+ {% if erasure_request.decided_at %} +

+ Decision {{ erasure_request.decided_at|date:"M j, Y H:i" }}. + {% if erasure_request.decision_reason %} + Reason: {{ erasure_request.decision_reason }} + {% endif %} +

+ {% endif %} + {% if erasure_request.status == "processed" %} +

+ Your account has been anonymized. Access has been revoked and SSH keys disabled. +

{% endif %}
- {% if key_form.non_field_errors %} -

{{ key_form.non_field_errors|striptags }}

- {% endif %} - - - {% else %} -

You do not have permission to add SSH keys.

- {% endif %} + {% endif %} - {% if ssh_keys %} -
- {% for key in ssh_keys %} -
-
-

{{ key.name }}

-

{{ key.fingerprint }}

-
-
- {% if key.is_active %} -
- - -
- - {% else %} - Revoked - {% endif %} -
+ {% if not erasure_request or erasure_request.status != "pending" %} +
+ {% csrf_token %} + +
+ + {{ erasure_form.reason }} + {% if erasure_form.reason.errors %} +

{{ erasure_form.reason.errors|striptags }}

+ {% endif %}
- {% endfor %} -
- {% else %} -

No SSH keys uploaded yet.

- {% endif %} + {% if erasure_form.non_field_errors %} +

{{ erasure_form.non_field_errors|striptags }}

+ {% endif %} +
+ +
+ + {% endif %} +
+ - -
-

Data erasure request

-

- Submit a GDPR erasure request to anonymize your account data. An administrator - must review and approve the request before processing. -

- - {% if erasure_request %} -
-
- Status: - - {{ erasure_request.status|capfirst }} - - Requested {{ erasure_request.requested_at|date:"M j, Y H:i" }} -
- {% if erasure_request.decided_at %} -

- Decision {{ erasure_request.decided_at|date:"M j, Y H:i" }}. - {% if erasure_request.decision_reason %} - Reason: {{ erasure_request.decision_reason }} - {% endif %} -

- {% endif %} - {% if erasure_request.status == "processed" %} -

- Your account has been anonymized. Access has been revoked and SSH keys disabled. -

- {% endif %} -
- {% endif %} - - {% if not erasure_request or erasure_request.status != "pending" %} -
- {% csrf_token %} - -
- -
- {{ erasure_form.reason }} -
- {% if erasure_form.reason.errors %} -

{{ erasure_form.reason.errors|striptags }}

- {% endif %} -
- {% if erasure_form.non_field_errors %} -

{{ erasure_form.non_field_errors|striptags }}

- {% endif %} - -
- {% endif %} -
{% endblock %} diff --git a/app/apps/servers/migrations/0009_server_heartbeat_fields.py b/app/apps/servers/migrations/0009_server_heartbeat_fields.py new file mode 100644 index 0000000..370f044 --- /dev/null +++ b/app/apps/servers/migrations/0009_server_heartbeat_fields.py @@ -0,0 +1,21 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("servers", "0008_remove_server_host_key"), + ] + + operations = [ + migrations.AddField( + model_name="server", + name="last_heartbeat_at", + field=models.DateTimeField(blank=True, db_index=True, null=True), + ), + migrations.AddField( + model_name="server", + name="last_ping_ms", + field=models.PositiveIntegerField(blank=True, null=True), + ), + ] diff --git a/app/apps/servers/models.py b/app/apps/servers/models.py index 2273afd..c9eae49 100644 --- a/app/apps/servers/models.py +++ b/app/apps/servers/models.py @@ -28,6 +28,8 @@ class Server(models.Model): agent_enrolled_at = models.DateTimeField(null=True, blank=True) agent_cert_fingerprint = models.CharField(max_length=128, null=True, blank=True) agent_cert_serial = models.CharField(max_length=64, null=True, blank=True) + last_heartbeat_at = models.DateTimeField(null=True, blank=True, db_index=True) + last_ping_ms = models.PositiveIntegerField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/app/apps/servers/templates/servers/_header.html b/app/apps/servers/templates/servers/_header.html index 82cc3ad..94c1651 100644 --- a/app/apps/servers/templates/servers/_header.html +++ b/app/apps/servers/templates/servers/_header.html @@ -1,42 +1,113 @@ -
-
-
- {{ server.initial }} +
+ + +
+
+
+ {{ server.initial }} +
+
+

{{ server.display_name }}

+

+ {{ server.hostname|default:server.ipv4|default:server.ipv6|default:"Unassigned" }} +

+
-
-

{{ server.display_name }}

-

- {{ server.hostname|default:server.ipv4|default:server.ipv6|default:"Unassigned" }} -

+
+
+ + +
+ + Back to servers +
- Back to servers
-
+ - +