mirror of https://github.com/zulip/zulip.git
activity: Add view for a remote server's audit logs.
Links to the audit log view via the remote support view in the remote server information section after the user counts.
This commit is contained in:
parent
ed0104ceb9
commit
52bc47a870
|
@ -213,6 +213,10 @@ class ActivityTest(ZulipTestCase):
|
||||||
result = self.client_get(f"/activity/plan_ledger/{plan.id}/")
|
result = self.client_get(f"/activity/plan_ledger/{plan.id}/")
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
with self.assert_database_query_count(7):
|
||||||
|
result = self.client_get(f"/activity/remote/logs/server/{server.uuid}/")
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
def test_get_remote_server_guest_and_non_guest_count(self) -> None:
|
def test_get_remote_server_guest_and_non_guest_count(self) -> None:
|
||||||
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
|
RemoteRealmAuditLog.objects.bulk_create([RemoteRealmAuditLog(**data) for data in data_list])
|
||||||
server_id = 1
|
server_id = 1
|
||||||
|
@ -374,3 +378,7 @@ class ActivityTest(ZulipTestCase):
|
||||||
with self.assert_database_query_count(11):
|
with self.assert_database_query_count(11):
|
||||||
result = self.client_get("/activity/remote")
|
result = self.client_get("/activity/remote")
|
||||||
self.assertEqual(result.status_code, 200)
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
||||||
|
with self.assert_database_query_count(7):
|
||||||
|
result = self.client_get(f"/activity/remote/logs/server/{remote_server.uuid}/")
|
||||||
|
self.assertEqual(result.status_code, 200)
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.conf.urls import include
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from django.views.generic import RedirectView, TemplateView
|
from django.views.generic import RedirectView, TemplateView
|
||||||
|
|
||||||
|
from corporate.views.audit_logs import get_remote_server_logs
|
||||||
from corporate.views.billing_page import (
|
from corporate.views.billing_page import (
|
||||||
billing_page,
|
billing_page,
|
||||||
remote_realm_billing_page,
|
remote_realm_billing_page,
|
||||||
|
@ -106,6 +107,7 @@ i18n_urlpatterns: Any = [
|
||||||
path("user_activity/<user_profile_id>/", get_user_activity),
|
path("user_activity/<user_profile_id>/", get_user_activity),
|
||||||
path("activity/remote", get_remote_server_activity),
|
path("activity/remote", get_remote_server_activity),
|
||||||
path("activity/remote/support", remote_servers_support, name="remote_servers_support"),
|
path("activity/remote/support", remote_servers_support, name="remote_servers_support"),
|
||||||
|
path("activity/remote/logs/server/<uuid>/", get_remote_server_logs),
|
||||||
path("activity/plan_ledger/<plan_id>/", get_plan_ledger),
|
path("activity/plan_ledger/<plan_id>/", get_plan_ledger),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
|
||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
from corporate.lib.activity import ActivityHeaderEntry, format_optional_datetime, make_table
|
||||||
|
from zerver.decorator import require_server_admin
|
||||||
|
from zerver.lib.typed_endpoint import PathOnly
|
||||||
|
from zerver.models.realm_audit_logs import AbstractRealmAuditLog, AuditLogEventType
|
||||||
|
from zilencer.models import RemoteRealmAuditLog, RemoteZulipServer, RemoteZulipServerAuditLog
|
||||||
|
|
||||||
|
USER_ROLES_KEY = "100: owner, 200: admin, 300: moderator, 400: member, 600: guest"
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_realm_host(audit_log: RemoteRealmAuditLog) -> str:
|
||||||
|
if audit_log.remote_realm is None:
|
||||||
|
# For pre-8.0 servers, we might only have the realm ID and thus
|
||||||
|
# no RemoteRealm object yet, so we show that information instead.
|
||||||
|
return f"N/A, realm ID: {audit_log.realm_id}"
|
||||||
|
return audit_log.remote_realm.host
|
||||||
|
|
||||||
|
|
||||||
|
def get_human_role_count_data(audit_log: RemoteRealmAuditLog | RemoteZulipServerAuditLog) -> str:
|
||||||
|
extra_data = audit_log.extra_data
|
||||||
|
role_count = extra_data.get(AbstractRealmAuditLog.ROLE_COUNT, {})
|
||||||
|
human_count_raw: dict[str, Any] = role_count.get(AbstractRealmAuditLog.ROLE_COUNT_HUMANS, {})
|
||||||
|
if human_count_raw == {}:
|
||||||
|
return "N/A"
|
||||||
|
human_count_string = ""
|
||||||
|
for role, count in human_count_raw.items():
|
||||||
|
if int(count) > 0:
|
||||||
|
human_count_string += f"{(role)}: {count}, "
|
||||||
|
return human_count_string.strip(", ")
|
||||||
|
|
||||||
|
|
||||||
|
@require_server_admin
|
||||||
|
def get_remote_server_logs(request: HttpRequest, *, uuid: PathOnly[str]) -> HttpResponse:
|
||||||
|
try:
|
||||||
|
remote_server = RemoteZulipServer.objects.get(uuid=uuid)
|
||||||
|
except RemoteZulipServer.DoesNotExist:
|
||||||
|
return HttpResponseNotFound()
|
||||||
|
|
||||||
|
remote_server_audit_logs = RemoteZulipServerAuditLog.objects.filter(
|
||||||
|
server=remote_server
|
||||||
|
).order_by("-id")
|
||||||
|
remote_realm_audit_logs = (
|
||||||
|
RemoteRealmAuditLog.objects.filter(server=remote_server)
|
||||||
|
.order_by("-id")
|
||||||
|
.select_related("remote_realm")
|
||||||
|
)
|
||||||
|
|
||||||
|
title = f"{remote_server.hostname}"
|
||||||
|
cols = [
|
||||||
|
"Event time",
|
||||||
|
"Event type",
|
||||||
|
"Audit log ID",
|
||||||
|
"Remote realm host",
|
||||||
|
"Role count: human",
|
||||||
|
]
|
||||||
|
|
||||||
|
def row(audit_log: RemoteRealmAuditLog | RemoteZulipServerAuditLog) -> list[Any]:
|
||||||
|
return [
|
||||||
|
audit_log.event_time,
|
||||||
|
AuditLogEventType(audit_log.event_type).name,
|
||||||
|
audit_log.id if isinstance(audit_log, RemoteRealmAuditLog) else f"S{audit_log.id}",
|
||||||
|
get_remote_realm_host(audit_log) if isinstance(audit_log, RemoteRealmAuditLog) else "",
|
||||||
|
get_human_role_count_data(audit_log)
|
||||||
|
if audit_log.event_type in AbstractRealmAuditLog.SYNCED_BILLING_EVENTS
|
||||||
|
else "",
|
||||||
|
]
|
||||||
|
|
||||||
|
remote_server_audit_log_rows = list(map(row, remote_server_audit_logs))
|
||||||
|
remote_realm_audit_log_rows = list(map(row, remote_realm_audit_logs))
|
||||||
|
rows = remote_server_audit_log_rows + remote_realm_audit_log_rows
|
||||||
|
|
||||||
|
header_entries = []
|
||||||
|
if remote_server.last_version is not None:
|
||||||
|
header_entries.append(
|
||||||
|
ActivityHeaderEntry(
|
||||||
|
name="Zulip version",
|
||||||
|
value=remote_server.last_version,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
header_entries.append(
|
||||||
|
ActivityHeaderEntry(
|
||||||
|
name="Last audit log update",
|
||||||
|
value=format_optional_datetime(remote_server.last_audit_log_update),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
header_entries.append(ActivityHeaderEntry(name="Role key", value=USER_ROLES_KEY))
|
||||||
|
|
||||||
|
content = make_table(title, cols, rows, header=header_entries)
|
||||||
|
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"corporate/activity/activity.html",
|
||||||
|
context=dict(
|
||||||
|
data=content,
|
||||||
|
title=title,
|
||||||
|
is_home=False,
|
||||||
|
),
|
||||||
|
)
|
|
@ -76,6 +76,7 @@
|
||||||
<b>Plan type</b>: {{ get_plan_type_name(remote_server.plan_type) }}<br />
|
<b>Plan type</b>: {{ get_plan_type_name(remote_server.plan_type) }}<br />
|
||||||
<b>Non-guest user count</b>: {{ remote_servers_support_data[remote_server.id].user_data.non_guest_user_count }}<br />
|
<b>Non-guest user count</b>: {{ remote_servers_support_data[remote_server.id].user_data.non_guest_user_count }}<br />
|
||||||
<b>Guest user count</b>: {{ remote_servers_support_data[remote_server.id].user_data.guest_user_count }}<br />
|
<b>Guest user count</b>: {{ remote_servers_support_data[remote_server.id].user_data.guest_user_count }}<br />
|
||||||
|
<a target="_blank" rel="noopener noreferrer" href="/activity/remote/logs/server/{{ remote_server.uuid }}/">View audit logs</a><br />
|
||||||
<br />
|
<br />
|
||||||
<b>Total mobile user count</b>: {{ remote_servers_support_data[remote_server.id].mobile_push_data.total_mobile_users }}<br />
|
<b>Total mobile user count</b>: {{ remote_servers_support_data[remote_server.id].mobile_push_data.total_mobile_users }}<br />
|
||||||
{% if remote_realms[remote_server.id] != [] %}
|
{% if remote_realms[remote_server.id] != [] %}
|
||||||
|
|
|
@ -57,6 +57,7 @@ not_yet_fully_covered = [
|
||||||
"corporate/views/plan_activity.py",
|
"corporate/views/plan_activity.py",
|
||||||
"corporate/views/realm_activity.py",
|
"corporate/views/realm_activity.py",
|
||||||
"corporate/views/remote_billing_page.py",
|
"corporate/views/remote_billing_page.py",
|
||||||
|
"corporate/views/audit_logs.py",
|
||||||
"corporate/views/support.py",
|
"corporate/views/support.py",
|
||||||
"corporate/lib/activity.py",
|
"corporate/lib/activity.py",
|
||||||
"corporate/lib/remote_billing_util.py",
|
"corporate/lib/remote_billing_util.py",
|
||||||
|
|
Loading…
Reference in New Issue