1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-08 19:26:48 +00:00
webvirtcloud/logs/apps.py
catborise 8c0fbff8f7 feat(logs): admin changes and logins are recorded on the Logs page
The Logs page shows who added, changed or deleted a compute, from web
or API, and every login, failed login, lockout and logout, with the
client address and a Result column (Done, Failed, Unknown for older
rows). A failed change is recorded with its reason and the error still
reaches the user; a refused one is recorded too.

- logs.audit.write saves each row in its own savepoint and never
  raises, so a log write cannot break or undo the action it records.
  The audit() context manager writes one row when the change ends.
- Logs gains success and ip; user, host and instance are wider and
  date is indexed. The page orders by date then id.
- A compute edit names the changed fields; an edit that changes
  nothing writes no row, and an empty password field is no change.
- A compute's VMs leaving the panel get a system row each: owners
  kept on the Removed VMs page, moved to another compute, restored,
  left without owners, or the removed record expired.
- Job rows carry the result. A job's log entry that cannot be
  computed is logged and skipped instead of rolling back the finish
  (test_service.py changes this on purpose).
- Logins are recorded by signal receivers; CustomLoginView is removed
  and the plain LoginView is used. A wrong OTP code is a failed login,
  counted by django-axes toward the lockout.

Panel records and host-side objects follow in the next layers.
2026-10-23 10:30:48 +03:00

38 lines
1.3 KiB
Python

from django.apps import AppConfig
def _login_row(request, user, message, success):
from logs.audit import request_ip, write
write(user, "-", "", message, success, request_ip(request) if request is not None else None)
def logged_in(sender, request, user, **kwargs):
_login_row(request, user.get_username(), "Login", True)
def login_failed(sender, credentials, request=None, **kwargs):
_login_row(request, credentials.get("username", ""), "Login failed", False)
def locked_out(sender, request, username, ip_address, **kwargs):
_login_row(request, username or "", "Locked out", False)
def logged_out(sender, request, user, **kwargs):
if user is not None:
_login_row(request, user.get_username(), "Logout", True)
class LogsConfig(AppConfig):
name = "logs"
default_auto_field = "django.db.models.AutoField"
def ready(self):
from axes.signals import user_locked_out
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
user_logged_in.connect(logged_in, dispatch_uid="logs.login")
user_login_failed.connect(login_failed, dispatch_uid="logs.login_failed")
user_locked_out.connect(locked_out, dispatch_uid="logs.locked_out")
user_logged_out.connect(logged_out, dispatch_uid="logs.logout")