varanus 0.1.0.dev1__tar.gz → 0.1.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/PKG-INFO +2 -2
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/pyproject.toml +2 -2
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/events.py +10 -0
- varanus-0.1.0.dev2/src/varanus/server/migrations/0003_alter_log_level.py +28 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/models.py +19 -7
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/search/base.py +16 -4
- varanus-0.1.0.dev2/src/varanus/server/search/facet.py +63 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/base.html +1 -5
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/environment_nodes.html +6 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/error.html +49 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/log.html +36 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/metric.html +40 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/node.html +44 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/node_environments.html +6 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/query.html +31 -0
- varanus-0.1.0.dev2/src/varanus/server/templates/site/details/request.html +136 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/errors.html +1 -1
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/logs.html +2 -2
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/metrics.html +1 -1
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/overview.html +10 -2
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/queries.html +1 -1
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/site/requests.html +1 -1
- varanus-0.1.0.dev2/src/varanus/server/templatetags/varanus.py +13 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/urls.py +22 -0
- varanus-0.1.0.dev2/src/varanus/server/views/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/views/site.py +57 -4
- varanus-0.1.0.dev1/src/varanus/server/search/facet.py +0 -59
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/error.html +0 -1
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/log.html +0 -1
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/metric.html +0 -1
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/query.html +0 -1
- varanus-0.1.0.dev1/src/varanus/server/templates/site/details/request.html +0 -44
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/README.md +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/apps.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/client.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/context.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/loggers.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/middleware.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/transport/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/transport/base.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/transport/database.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/client/transport/http.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/__main__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/admin.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/apps.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/asgi.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/context_processors.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/management/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/management/commands/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/management/commands/migrateall.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/middleware.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/migrations/0001_initial.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/migrations/0002_context_node_error_node_log_node_metric_node_and_more.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/migrations/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/router.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/search/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/search/date.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/settings.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/static/css/varanus.css +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/static/js/varanus.js +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/base.html +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/dashboard.html +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/registration/login.html +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/search/daterange.html +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/search/multifacet.html +0 -0
- {varanus-0.1.0.dev1/src/varanus/server/views → varanus-0.1.0.dev2/src/varanus/server/templatetags}/__init__.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/views/api.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/views/base.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/views/dashboard.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/wsgi.py +0 -0
- {varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: varanus
|
|
3
|
-
Version: 0.1.0.
|
|
3
|
+
Version: 0.1.0.dev2
|
|
4
4
|
Summary: Django application monitoring.
|
|
5
5
|
Requires-Dist: httpx>=0.27.0
|
|
6
6
|
Requires-Dist: msgspec>=0.19.0
|
|
@@ -11,7 +11,7 @@ Requires-Dist: granian>=2.4.2 ; extra == 'server'
|
|
|
11
11
|
Requires-Dist: psycopg[binary]>=3.2.1 ; extra == 'server'
|
|
12
12
|
Requires-Dist: websockets>=13.0 ; extra == 'server'
|
|
13
13
|
Requires-Dist: whitenoise>=6.7.0 ; extra == 'server'
|
|
14
|
-
Requires-Python: >=3.
|
|
14
|
+
Requires-Python: >=3.11
|
|
15
15
|
Provides-Extra: server
|
|
16
16
|
Description-Content-Type: text/markdown
|
|
17
17
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "varanus"
|
|
3
|
-
version = "0.1.0.
|
|
3
|
+
version = "0.1.0.dev2"
|
|
4
4
|
description = "Django application monitoring."
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
requires-python = ">=3.
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"httpx>=0.27.0",
|
|
9
9
|
"msgspec>=0.19.0",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import inspect
|
|
1
2
|
import logging
|
|
2
3
|
from datetime import datetime, timezone
|
|
3
4
|
|
|
@@ -25,6 +26,7 @@ class ErrorLine(Struct):
|
|
|
25
26
|
lineno: int | None
|
|
26
27
|
function: str | None
|
|
27
28
|
module: str | None
|
|
29
|
+
linesrc: str | None
|
|
28
30
|
locals: dict[str, str]
|
|
29
31
|
|
|
30
32
|
|
|
@@ -52,6 +54,13 @@ class Error(Event):
|
|
|
52
54
|
abs_path = f_code.co_filename if f_code else None
|
|
53
55
|
function = f_code.co_name if f_code else None
|
|
54
56
|
lineno = getattr(tb, "tb_lineno", None)
|
|
57
|
+
linesrc = None
|
|
58
|
+
if f_code and lineno:
|
|
59
|
+
try:
|
|
60
|
+
source, start = inspect.getsourcelines(f_code)
|
|
61
|
+
linesrc = source[lineno - start].strip()
|
|
62
|
+
except (OSError, TypeError):
|
|
63
|
+
pass
|
|
55
64
|
module = f_globals.get("__name__", "")
|
|
56
65
|
lines.append(
|
|
57
66
|
ErrorLine(
|
|
@@ -59,6 +68,7 @@ class Error(Event):
|
|
|
59
68
|
lineno=lineno,
|
|
60
69
|
function=function,
|
|
61
70
|
module=module,
|
|
71
|
+
linesrc=linesrc,
|
|
62
72
|
locals={name: repr(val) for name, val in f_locals.items()},
|
|
63
73
|
)
|
|
64
74
|
)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Generated by Django 5.2.8 on 2025-11-17 19:51
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("varanus", "0002_context_node_error_node_log_node_metric_node_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AlterField(
|
|
13
|
+
model_name="log",
|
|
14
|
+
name="level",
|
|
15
|
+
field=models.IntegerField(
|
|
16
|
+
blank=True,
|
|
17
|
+
choices=[
|
|
18
|
+
(50, "CRITICAL"),
|
|
19
|
+
(40, "ERROR"),
|
|
20
|
+
(30, "WARNING"),
|
|
21
|
+
(20, "INFO"),
|
|
22
|
+
(10, "DEBUG"),
|
|
23
|
+
(0, "NOTSET"),
|
|
24
|
+
],
|
|
25
|
+
null=True,
|
|
26
|
+
),
|
|
27
|
+
),
|
|
28
|
+
]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import http
|
|
3
|
-
import logging
|
|
4
3
|
import secrets
|
|
5
4
|
from typing import Self
|
|
6
5
|
|
|
@@ -161,7 +160,16 @@ class Node(EnvironmentModel):
|
|
|
161
160
|
]
|
|
162
161
|
|
|
163
162
|
def __str__(self):
|
|
164
|
-
return self.name
|
|
163
|
+
return f"{self.environment}@{self.name}"
|
|
164
|
+
|
|
165
|
+
def get_absolute_url(self):
|
|
166
|
+
return reverse(
|
|
167
|
+
"node-details",
|
|
168
|
+
kwargs={
|
|
169
|
+
"slug": self.site.slug,
|
|
170
|
+
"pk": self.pk,
|
|
171
|
+
},
|
|
172
|
+
)
|
|
165
173
|
|
|
166
174
|
@classmethod
|
|
167
175
|
def update(cls, info: events.NodeInfo, site: Site, environment: str = ""):
|
|
@@ -450,9 +458,17 @@ class Error(ContextualModel, FingerprintModel):
|
|
|
450
458
|
|
|
451
459
|
|
|
452
460
|
class Log(ContextualModel, FingerprintModel):
|
|
461
|
+
class Level(models.IntegerChoices):
|
|
462
|
+
CRITICAL = 50, "CRITICAL"
|
|
463
|
+
ERROR = 40, "ERROR"
|
|
464
|
+
WARNING = 30, "WARNING"
|
|
465
|
+
INFO = 20, "INFO"
|
|
466
|
+
DEBUG = 10, "DEBUG"
|
|
467
|
+
NOTSET = 0, "NOTSET"
|
|
468
|
+
|
|
453
469
|
message = models.TextField()
|
|
454
470
|
name = models.CharField(max_length=200, blank=True)
|
|
455
|
-
level = models.IntegerField(null=True, blank=True)
|
|
471
|
+
level = models.IntegerField(null=True, blank=True, choices=Level)
|
|
456
472
|
file = models.TextField(blank=True)
|
|
457
473
|
lineno = models.IntegerField(null=True, blank=True)
|
|
458
474
|
error = models.ForeignKey(
|
|
@@ -481,10 +497,6 @@ class Log(ContextualModel, FingerprintModel):
|
|
|
481
497
|
**extra,
|
|
482
498
|
)
|
|
483
499
|
|
|
484
|
-
@property
|
|
485
|
-
def level_name(self):
|
|
486
|
-
return logging.getLevelName(self.level) if self.level is not None else "UNKNOWN"
|
|
487
|
-
|
|
488
500
|
def fingerprint_parts(self):
|
|
489
501
|
return (self.message, self.name, self.level, self.file, self.lineno)
|
|
490
502
|
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
from typing import ClassVar, Mapping, Self
|
|
1
|
+
from typing import Callable, ClassVar, Literal, Mapping, Self, TypeAlias
|
|
2
2
|
|
|
3
3
|
from django.db.models import QuerySet
|
|
4
4
|
from django.http import HttpRequest
|
|
5
5
|
from django.template import loader
|
|
6
6
|
from django.utils.datastructures import MultiValueDict
|
|
7
|
+
from django.utils.functional import Promise
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
StringValues: TypeAlias = dict[str, list[str]]
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def string_data(data) -> StringValues:
|
|
@@ -26,12 +27,23 @@ def string_data(data) -> StringValues:
|
|
|
26
27
|
class SearchField:
|
|
27
28
|
template_name: str
|
|
28
29
|
|
|
29
|
-
def __init__(
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
label=None,
|
|
33
|
+
field_name=None,
|
|
34
|
+
empty_label="NULL",
|
|
35
|
+
# These belong on Facet
|
|
36
|
+
choice_label: Callable[..., str | Promise] | None = None,
|
|
37
|
+
order: Literal["value", "count"] = "value",
|
|
38
|
+
):
|
|
30
39
|
self.name = ""
|
|
31
40
|
self.label = label or ""
|
|
32
41
|
self.field_name = field_name or ""
|
|
33
42
|
self.prefix = ""
|
|
34
43
|
self.id = ""
|
|
44
|
+
self.empty_label = empty_label
|
|
45
|
+
self.choice_label = choice_label
|
|
46
|
+
self.order = order
|
|
35
47
|
|
|
36
48
|
def __set_name__(self, owner, name: str):
|
|
37
49
|
# print("__set_name__", owner, name)
|
|
@@ -40,7 +52,7 @@ class SearchField:
|
|
|
40
52
|
self.prefix = f"{name}_"
|
|
41
53
|
self.id = f"id_{name}"
|
|
42
54
|
if not self.label:
|
|
43
|
-
self.label = name.capitalize()
|
|
55
|
+
self.label = name.capitalize().replace("__", " ")
|
|
44
56
|
if not self.field_name:
|
|
45
57
|
self.field_name = name
|
|
46
58
|
if not hasattr(owner, "_fields"):
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import operator
|
|
3
|
+
from typing import Any, ClassVar
|
|
4
|
+
|
|
5
|
+
from django.db.models import Count, Q, QuerySet
|
|
6
|
+
|
|
7
|
+
from .base import SearchField, StringValues
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Choice:
|
|
11
|
+
id: str
|
|
12
|
+
value: str
|
|
13
|
+
label: str
|
|
14
|
+
count: int
|
|
15
|
+
|
|
16
|
+
def __init__(self, field: SearchField, value: Any, count: int):
|
|
17
|
+
self.value = self.stringify(value)
|
|
18
|
+
if callable(field.choice_label):
|
|
19
|
+
self.label = field.choice_label(value)
|
|
20
|
+
else:
|
|
21
|
+
self.label = self.value or field.empty_label
|
|
22
|
+
self.id = field.id + "_" + self.value.replace(" ", "")
|
|
23
|
+
self.count = count
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def stringify(cls, value):
|
|
27
|
+
return str(value) if value is not None else ""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Facet(SearchField):
|
|
31
|
+
choice_class: ClassVar[type[Choice]] = Choice
|
|
32
|
+
|
|
33
|
+
def get_choices(self, queryset):
|
|
34
|
+
choice_qs = queryset.values_list(self.field_name).annotate(num=Count("*"))
|
|
35
|
+
match self.order:
|
|
36
|
+
case "value":
|
|
37
|
+
choice_qs = choice_qs.order_by(self.field_name)
|
|
38
|
+
case "count":
|
|
39
|
+
choice_qs = choice_qs.order_by("-num")
|
|
40
|
+
return [self.choice_class(self, c, num) for c, num in choice_qs]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class MultiFacet(Facet):
|
|
44
|
+
template_name = "search/multifacet.html"
|
|
45
|
+
|
|
46
|
+
def apply(self, queryset: QuerySet, field_data: StringValues) -> QuerySet:
|
|
47
|
+
filters = []
|
|
48
|
+
if selected := set(field_data.get("s", [])):
|
|
49
|
+
if "" in selected:
|
|
50
|
+
selected.discard("")
|
|
51
|
+
filters.append(Q(**{f"{self.field_name}__isnull": True}))
|
|
52
|
+
if selected:
|
|
53
|
+
filters.append(Q(**{f"{self.field_name}__in": selected}))
|
|
54
|
+
if filters:
|
|
55
|
+
queryset = queryset.filter(functools.reduce(operator.or_, filters))
|
|
56
|
+
return queryset
|
|
57
|
+
|
|
58
|
+
def get_context(self, queryset: QuerySet, field_data: StringValues) -> dict:
|
|
59
|
+
return {
|
|
60
|
+
**super().get_context(queryset, field_data),
|
|
61
|
+
"choices": self.get_choices(queryset),
|
|
62
|
+
"selected": field_data.get("s", []),
|
|
63
|
+
}
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
<form action="" method="get">
|
|
33
33
|
{{ search.render|safe }}
|
|
34
34
|
<button class="btn btn-primary">Filter</button>
|
|
35
|
+
<a href="{{ request.path }}" class="btn btn-secondary">Clear</a>
|
|
35
36
|
</form>
|
|
36
37
|
{% endblock search %}
|
|
37
38
|
</div>
|
|
@@ -42,11 +43,6 @@
|
|
|
42
43
|
</div>
|
|
43
44
|
|
|
44
45
|
<div class="offcanvas offcanvas-end" style="width:33%" tabindex="-1" id="inspector">
|
|
45
|
-
<div class="offcanvas-header">
|
|
46
|
-
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="offcanvas-body" id="inspector-content">
|
|
49
|
-
</div>
|
|
50
46
|
</div>
|
|
51
47
|
{% endblock content %}
|
|
52
48
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
{% load varanus %}
|
|
3
|
+
|
|
4
|
+
<div class="offcanvas-header">
|
|
5
|
+
<h5 class="offcanvas-title">
|
|
6
|
+
{% if context.request %}
|
|
7
|
+
<a href="{{ context.request.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ context.request.get_absolute_url }}" hx-target="#inspector"><i class="bi bi-chevron-left"></i> {{ context }}</a>
|
|
8
|
+
{% endif %}
|
|
9
|
+
</h5>
|
|
10
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="offcanvas-body">
|
|
13
|
+
<table class="table table-sm">
|
|
14
|
+
<tbody>
|
|
15
|
+
<tr>
|
|
16
|
+
<th>{% trans "Kind" %}</th>
|
|
17
|
+
<td>{{ object.kind }}</td>
|
|
18
|
+
</tr>
|
|
19
|
+
<tr>
|
|
20
|
+
<th>{% trans "Module" %}</th>
|
|
21
|
+
<td>{{ object.module }}</td>
|
|
22
|
+
</tr>
|
|
23
|
+
<tr>
|
|
24
|
+
<th>{% trans "Message" %}</th>
|
|
25
|
+
<td>{{ object.message }}</td>
|
|
26
|
+
</tr>
|
|
27
|
+
</tbody>
|
|
28
|
+
</table>
|
|
29
|
+
|
|
30
|
+
<ul class="list-unstyled">
|
|
31
|
+
{% for line in object.lines %}
|
|
32
|
+
<li>
|
|
33
|
+
<p class="mb-3 p-1 border-bottom">
|
|
34
|
+
<strong>{{ line.module }}.{{ line.function }}</strong><br />
|
|
35
|
+
<small class="text-secondary">{{ line.file|truncatepath }}:{{ line.lineno }}</small>
|
|
36
|
+
{% if line.linesrc %}
|
|
37
|
+
<br /><code>{{ line.linesrc }}</code>
|
|
38
|
+
{% endif %}
|
|
39
|
+
</p>
|
|
40
|
+
<dl class="ms-3">
|
|
41
|
+
{% for name, value in line.locals.items %}
|
|
42
|
+
<dt class="font-monospace"><code>{{ name }}</code></dt>
|
|
43
|
+
<dd class="ms-3"><pre>{{ value }}</pre></dd>
|
|
44
|
+
{% endfor %}
|
|
45
|
+
</dl>
|
|
46
|
+
</li>
|
|
47
|
+
{% endfor %}
|
|
48
|
+
</ul>
|
|
49
|
+
</div>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
|
|
3
|
+
<div class="offcanvas-header">
|
|
4
|
+
<h5 class="offcanvas-title">
|
|
5
|
+
{% if context.request %}
|
|
6
|
+
<a href="{{ context.request.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ context.request.get_absolute_url }}" hx-target="#inspector"><i class="bi bi-chevron-left"></i> {{ context }}</a>
|
|
7
|
+
{% endif %}
|
|
8
|
+
</h5>
|
|
9
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="offcanvas-body">
|
|
12
|
+
<table class="table table-sm">
|
|
13
|
+
<tbody>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>{% trans "Name" %}</th>
|
|
16
|
+
<td>{{ object.name }}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<th>{% trans "Level" %}</th>
|
|
20
|
+
<td>{{ object.get_level_display }}</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th>{% trans "File" %}</th>
|
|
24
|
+
<td>{{ object.file }}:{{ object.lineno }}</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<th>{% trans "Message" %}</th>
|
|
28
|
+
<td>{{ object.message }}</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<th>{% trans "Error" %}</th>
|
|
32
|
+
<td>{{ object.error }}</td>
|
|
33
|
+
</tr>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
|
36
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
|
|
3
|
+
<div class="offcanvas-header">
|
|
4
|
+
<h5 class="offcanvas-title">
|
|
5
|
+
{% if context.request %}
|
|
6
|
+
<a href="{{ context.request.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ context.request.get_absolute_url }}" hx-target="#inspector"><i class="bi bi-chevron-left"></i> {{ context }}</a>
|
|
7
|
+
{% endif %}
|
|
8
|
+
</h5>
|
|
9
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="offcanvas-body">
|
|
12
|
+
<table class="table table-sm">
|
|
13
|
+
<tbody>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>{% trans "Name" %}</th>
|
|
16
|
+
<td>{{ object.name }}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<th>{% trans "Count" %}</th>
|
|
20
|
+
<td>{{ object.agg_count }}</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th>{% trans "Sum" %}</th>
|
|
24
|
+
<td>{{ object.agg_sum }}</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<th>{% trans "Average" %}</th>
|
|
28
|
+
<td>{{ object.agg_avg }}</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<th>{% trans "Min" %}</th>
|
|
32
|
+
<td>{{ object.agg_min }}</td>
|
|
33
|
+
</tr>
|
|
34
|
+
<tr>
|
|
35
|
+
<th>{% trans "Max" %}</th>
|
|
36
|
+
<td>{{ object.agg_max }}</td>
|
|
37
|
+
</tr>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
|
|
3
|
+
<div class="offcanvas-header">
|
|
4
|
+
<h5 class="offcanvas-title">{{ node }}</h5>
|
|
5
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="offcanvas-body">
|
|
8
|
+
<table class="table table-sm table-striped">
|
|
9
|
+
<thead class="table-header">
|
|
10
|
+
<tr>
|
|
11
|
+
<th>{% trans "Package" %}</th>
|
|
12
|
+
<th>{% trans "Version" %}</th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody>
|
|
16
|
+
{% for p in node.packages.all %}
|
|
17
|
+
<tr>
|
|
18
|
+
<td>{{ p.package }}</td>
|
|
19
|
+
<td>{{ p.version }}</td>
|
|
20
|
+
</tr>
|
|
21
|
+
{% endfor %}
|
|
22
|
+
</tbody>
|
|
23
|
+
</table>
|
|
24
|
+
|
|
25
|
+
{% for update in node.updates.all %}
|
|
26
|
+
<h6><em>{{ update.timestamp }}</em></h6>
|
|
27
|
+
<dl>
|
|
28
|
+
{% if update.installed %}
|
|
29
|
+
<dt>{% trans "Installed" %}</dt>
|
|
30
|
+
<dd>{{ update.installed }}</dd>
|
|
31
|
+
{% endif %}
|
|
32
|
+
|
|
33
|
+
{% if update.updated %}
|
|
34
|
+
<dt>{% trans "Updated" %}</dt>
|
|
35
|
+
<dd>{{ update.updated }}</dd>
|
|
36
|
+
{% endif %}
|
|
37
|
+
|
|
38
|
+
{% if update.removed %}
|
|
39
|
+
<dt>{% trans "Removed" %}</dt>
|
|
40
|
+
<dd>{{ update.removed }}</dd>
|
|
41
|
+
{% endif %}
|
|
42
|
+
</dl>
|
|
43
|
+
{% endfor %}
|
|
44
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
|
|
3
|
+
<div class="offcanvas-header">
|
|
4
|
+
<h5 class="offcanvas-title">
|
|
5
|
+
{% if context.request %}
|
|
6
|
+
<a href="{{ context.request.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ context.request.get_absolute_url }}" hx-target="#inspector"><i class="bi bi-chevron-left"></i> {{ context }}</a>
|
|
7
|
+
{% endif %}
|
|
8
|
+
</h5>
|
|
9
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="offcanvas-body">
|
|
12
|
+
<table class="table table-sm">
|
|
13
|
+
<tbody>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>{% trans "Database" %}</th>
|
|
16
|
+
<td>{{ object.db }}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<th>{% trans "Elapsed" %}</th>
|
|
20
|
+
<td>{{ object.elapsed_ms }}ms</td>
|
|
21
|
+
</tr>
|
|
22
|
+
{% if object.params %}
|
|
23
|
+
<tr>
|
|
24
|
+
<th>{% trans "Parameters" %}</th>
|
|
25
|
+
<td>{{ object.params }}</td>
|
|
26
|
+
</tr>
|
|
27
|
+
{% endif %}
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
<code><pre>{{ object.sql_formatted }}</pre></code>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
|
|
3
|
+
<div class="offcanvas-header">
|
|
4
|
+
<h5 class="offcanvas-title">{{ object.method }} {{ object.path }}</h5>
|
|
5
|
+
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
6
|
+
</div>
|
|
7
|
+
<div class="offcanvas-body">
|
|
8
|
+
<table class="table table-sm">
|
|
9
|
+
<tbody>
|
|
10
|
+
<tr>
|
|
11
|
+
<th>{% trans "Host" %}</th>
|
|
12
|
+
<td>{{ object.host }}</td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<th>{% trans "Node" %}</th>
|
|
16
|
+
<td>{{ object.node }}</td>
|
|
17
|
+
</tr>
|
|
18
|
+
<tr>
|
|
19
|
+
<th>{% trans "Request" %}</th>
|
|
20
|
+
<td>{{ object.method }} {{ object.path }}</td>
|
|
21
|
+
</tr>
|
|
22
|
+
<tr>
|
|
23
|
+
<th>{% trans "Status" %}</th>
|
|
24
|
+
<td>{{ object.http_status }}</td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<th>{% trans "IP" %}</th>
|
|
28
|
+
<td>{{ object.ip }}</td>
|
|
29
|
+
</tr>
|
|
30
|
+
<tr>
|
|
31
|
+
<th>{% trans "User" %}</th>
|
|
32
|
+
<td>{{ object.user }}</td>
|
|
33
|
+
</tr>
|
|
34
|
+
</tbody>
|
|
35
|
+
</table>
|
|
36
|
+
|
|
37
|
+
{% if object.headers %}
|
|
38
|
+
<h5>{% trans "Headers" %}</h5>
|
|
39
|
+
<table class="table table-sm">
|
|
40
|
+
<tbody>
|
|
41
|
+
{% for name, value in object.headers.items %}
|
|
42
|
+
<tr>
|
|
43
|
+
<th>{{ name }}</th>
|
|
44
|
+
<td>{{ value }}</th>
|
|
45
|
+
</tr>
|
|
46
|
+
{% endfor %}
|
|
47
|
+
</tbody>
|
|
48
|
+
</table>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
{% if context.errors.exists %}
|
|
52
|
+
<table class="table table-sm">
|
|
53
|
+
<thead class="table-header">
|
|
54
|
+
<tr>
|
|
55
|
+
<th>{% trans "Error" %}</th>
|
|
56
|
+
<th>{% trans "Kind" %}</th>
|
|
57
|
+
<th>{% trans "Module" %}</th>
|
|
58
|
+
</tr>
|
|
59
|
+
</thead>
|
|
60
|
+
<tbody>
|
|
61
|
+
{% for err in context.errors.all %}
|
|
62
|
+
<tr>
|
|
63
|
+
<td>
|
|
64
|
+
<a href="{{ err.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ err.get_absolute_url }}" hx-target="#inspector">{{ err.message }}</a>
|
|
65
|
+
</td>
|
|
66
|
+
<td>{{ err.kind }}</td>
|
|
67
|
+
<td>{{ err.module }}</td>
|
|
68
|
+
{% endfor %}
|
|
69
|
+
</tbody>
|
|
70
|
+
</table>
|
|
71
|
+
{% endif %}
|
|
72
|
+
|
|
73
|
+
{% if context.logs.exists %}
|
|
74
|
+
<table class="table table-sm">
|
|
75
|
+
<thead class="table-header">
|
|
76
|
+
<tr>
|
|
77
|
+
<th>{% trans "Log" %}</th>
|
|
78
|
+
<th>{% trans "Level" %}</th>
|
|
79
|
+
</tr>
|
|
80
|
+
</thead>
|
|
81
|
+
<tbody>
|
|
82
|
+
{% for log in context.logs.all %}
|
|
83
|
+
<tr>
|
|
84
|
+
<td>
|
|
85
|
+
<a href="{{ log.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ log.get_absolute_url }}" hx-target="#inspector">{{ log.message }}</a>
|
|
86
|
+
</td>
|
|
87
|
+
<td>{{ log.get_level_display }}</td>
|
|
88
|
+
{% endfor %}
|
|
89
|
+
</tbody>
|
|
90
|
+
</table>
|
|
91
|
+
{% endif %}
|
|
92
|
+
|
|
93
|
+
{% if context.queries.exists %}
|
|
94
|
+
<table class="table table-sm">
|
|
95
|
+
<thead class="table-header">
|
|
96
|
+
<tr>
|
|
97
|
+
<th>{% trans "Query" %}</th>
|
|
98
|
+
<th>{% trans "Elapsed" %}</th>
|
|
99
|
+
</tr>
|
|
100
|
+
</thead>
|
|
101
|
+
<tbody>
|
|
102
|
+
{% for query in context.queries.all %}
|
|
103
|
+
<tr>
|
|
104
|
+
<td>
|
|
105
|
+
<a href="{{ query.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ query.get_absolute_url }}" hx-target="#inspector">{{ query.sql_summary }}</a>
|
|
106
|
+
</td>
|
|
107
|
+
<td>{{ query.elapsed_ms }}ms</td>
|
|
108
|
+
{% endfor %}
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
{% endif %}
|
|
112
|
+
|
|
113
|
+
{% if context.metrics.exists %}
|
|
114
|
+
<table class="table table-sm">
|
|
115
|
+
<thead class="table-header">
|
|
116
|
+
<tr>
|
|
117
|
+
<th>{% trans "Metric" %}</th>
|
|
118
|
+
<th>{% trans "Count" %}</th>
|
|
119
|
+
<th>{% trans "Sum" %}</th>
|
|
120
|
+
<th>{% trans "Average" %}</th>
|
|
121
|
+
</tr>
|
|
122
|
+
</thead>
|
|
123
|
+
<tbody>
|
|
124
|
+
{% for m in context.metrics.all %}
|
|
125
|
+
<tr>
|
|
126
|
+
<td>
|
|
127
|
+
<a href="{{ m.get_absolute_url }}" data-bs-target="#inspector" hx-get="{{ m.get_absolute_url }}" hx-target="#inspector">{{ m.name }}</a>
|
|
128
|
+
</td>
|
|
129
|
+
<td>{{ m.agg_count }}</td>
|
|
130
|
+
<td>{{ m.agg_sum }}</td>
|
|
131
|
+
<td>{{ m.agg_avg }}</td>
|
|
132
|
+
{% endfor %}
|
|
133
|
+
</tbody>
|
|
134
|
+
</table>
|
|
135
|
+
{% endif %}
|
|
136
|
+
</div>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
{% for e in results %}
|
|
18
18
|
<tr>
|
|
19
19
|
<td>
|
|
20
|
-
<a href="{{ e.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ e.get_absolute_url }}" hx-target="#inspector
|
|
20
|
+
<a href="{{ e.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ e.get_absolute_url }}" hx-target="#inspector">{{ e.context }}</a>
|
|
21
21
|
</td>
|
|
22
22
|
<td>{{ e.kind }}</td>
|
|
23
23
|
<td>{{ e.message }}</td>
|
|
@@ -18,9 +18,9 @@
|
|
|
18
18
|
{% for log in results %}
|
|
19
19
|
<tr>
|
|
20
20
|
<td>
|
|
21
|
-
<a href="{{ log.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ log.get_absolute_url }}" hx-target="#inspector
|
|
21
|
+
<a href="{{ log.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ log.get_absolute_url }}" hx-target="#inspector">{{ log.context }}</a>
|
|
22
22
|
</td>
|
|
23
|
-
<td>{{ log.
|
|
23
|
+
<td>{{ log.get_level_display }}</td>
|
|
24
24
|
<td>{{ log.message }}</td>
|
|
25
25
|
<td>{{ log.name }}</td>
|
|
26
26
|
<td>{{ log.file }}:{{ log.lineno }}</td>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{% for m in results %}
|
|
21
21
|
<tr>
|
|
22
22
|
<td>
|
|
23
|
-
<a href="{{ m.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ m.get_absolute_url }}" hx-target="#inspector
|
|
23
|
+
<a href="{{ m.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ m.get_absolute_url }}" hx-target="#inspector">{{ m.context }}</a>
|
|
24
24
|
</td>
|
|
25
25
|
<td>{{ m.name }}</td>
|
|
26
26
|
<td>{{ m.agg_count }}</td>
|
|
@@ -26,6 +26,7 @@ varanus.client.setup(
|
|
|
26
26
|
<tr>
|
|
27
27
|
<th>{% trans "Environment" %}</th>
|
|
28
28
|
<th>{% trans "Node" %}</th>
|
|
29
|
+
<th>{% trans "Packages" %}</th>
|
|
29
30
|
<th>{% trans "Platform" %}</th>
|
|
30
31
|
<th>{% trans "Python Version" %}</th>
|
|
31
32
|
<th>{% trans "First Seen" %}</th>
|
|
@@ -35,8 +36,15 @@ varanus.client.setup(
|
|
|
35
36
|
<tbody>
|
|
36
37
|
{% for node in nodes %}
|
|
37
38
|
<tr>
|
|
38
|
-
<td>
|
|
39
|
-
|
|
39
|
+
<td>
|
|
40
|
+
<a href="{% url 'environment-nodes' site.slug node.environment %}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{% url 'environment-nodes' site.slug node.environment %}" hx-target="#inspector">{{ node.environment }}</a>
|
|
41
|
+
</td>
|
|
42
|
+
<td>
|
|
43
|
+
<a href="{% url 'node-environments' site.slug node.name %}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{% url 'node-environments' site.slug node.name %}" hx-target="#inspector">{{ node.name }}</a>
|
|
44
|
+
</td>
|
|
45
|
+
<td>
|
|
46
|
+
<a href="{% url 'node-details' site.slug node.pk %}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{% url 'node-details' site.slug node.pk %}" hx-target="#inspector">{{ node.num_packages }}</a>
|
|
47
|
+
</td>
|
|
40
48
|
<td>{{ node.platform }}</td>
|
|
41
49
|
<td>{{ node.python_version }}</td>
|
|
42
50
|
<td>{{ node.first_seen }}</td>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
{% for q in results %}
|
|
21
21
|
<tr>
|
|
22
22
|
<td>
|
|
23
|
-
<a href="{{ q.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ q.get_absolute_url }}" hx-target="#inspector
|
|
23
|
+
<a href="{{ q.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ q.get_absolute_url }}" hx-target="#inspector">{{ q.context }}</a>
|
|
24
24
|
</td>
|
|
25
25
|
<td>{{ q.command }}</td>
|
|
26
26
|
<td>{{ q.db }}</td>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
{% for r in results %}
|
|
19
19
|
<tr>
|
|
20
20
|
<td>
|
|
21
|
-
<a href="{{ r.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ r.get_absolute_url }}" hx-target="#inspector
|
|
21
|
+
<a href="{{ r.get_absolute_url }}" data-bs-toggle="offcanvas" data-bs-target="#inspector" hx-get="{{ r.get_absolute_url }}" hx-target="#inspector">{{ r.path }}</a>
|
|
22
22
|
</td>
|
|
23
23
|
<td>{{ r.size|filesizeformat }}</td>
|
|
24
24
|
<td>{{ r.context.elapsed_ms }}ms</td>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from django import template
|
|
2
|
+
|
|
3
|
+
register = template.Library()
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@register.filter
|
|
7
|
+
def truncatepath(path: str, num: int = 50):
|
|
8
|
+
if len(path) > num:
|
|
9
|
+
parts = path.split("/")
|
|
10
|
+
while len("/".join(parts)) > num:
|
|
11
|
+
parts.pop(0)
|
|
12
|
+
return "…" + "/".join(parts)
|
|
13
|
+
return path
|
|
@@ -29,6 +29,28 @@ urlpatterns = [
|
|
|
29
29
|
site.Details.as_view(),
|
|
30
30
|
name="event-details",
|
|
31
31
|
),
|
|
32
|
+
path(
|
|
33
|
+
"node/",
|
|
34
|
+
include(
|
|
35
|
+
[
|
|
36
|
+
path(
|
|
37
|
+
"name/<name>/",
|
|
38
|
+
site.NodeEnvironments.as_view(),
|
|
39
|
+
name="node-environments",
|
|
40
|
+
),
|
|
41
|
+
path(
|
|
42
|
+
"environment/<environment>/",
|
|
43
|
+
site.EnvironmentNodes.as_view(),
|
|
44
|
+
name="environment-nodes",
|
|
45
|
+
),
|
|
46
|
+
path(
|
|
47
|
+
"<pk>/",
|
|
48
|
+
site.NodeDetails.as_view(),
|
|
49
|
+
name="node-details",
|
|
50
|
+
),
|
|
51
|
+
]
|
|
52
|
+
),
|
|
53
|
+
),
|
|
32
54
|
]
|
|
33
55
|
),
|
|
34
56
|
),
|
|
File without changes
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
from typing import ClassVar
|
|
2
2
|
|
|
3
3
|
from django.contrib.contenttypes.models import ContentType
|
|
4
|
-
from django.db.models import OuterRef, QuerySet, Subquery
|
|
4
|
+
from django.db.models import Count, OuterRef, QuerySet, Subquery
|
|
5
|
+
from django.shortcuts import get_object_or_404
|
|
6
|
+
from django.utils.functional import Promise
|
|
5
7
|
|
|
6
|
-
from ..models import Context
|
|
8
|
+
from ..models import Context, Log
|
|
7
9
|
from ..search import DateRange, MultiFacet, Search
|
|
8
10
|
from .base import SiteView
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
def level_name(level) -> str | Promise:
|
|
14
|
+
try:
|
|
15
|
+
return Log.Level(level).label
|
|
16
|
+
except ValueError:
|
|
17
|
+
return str(level)
|
|
18
|
+
|
|
19
|
+
|
|
11
20
|
class Overview(SiteView):
|
|
12
21
|
template_name = "site/overview.html"
|
|
13
22
|
|
|
@@ -18,6 +27,7 @@ class Overview(SiteView):
|
|
|
18
27
|
.order_by("-timestamp")
|
|
19
28
|
.values("timestamp")[:1]
|
|
20
29
|
),
|
|
30
|
+
num_packages=Count("packages"),
|
|
21
31
|
)
|
|
22
32
|
return {
|
|
23
33
|
"host": self.request.get_host(),
|
|
@@ -36,7 +46,7 @@ class SearchView(SiteView):
|
|
|
36
46
|
search = self.search_class(self.get_queryset(), self.request.GET)
|
|
37
47
|
return {
|
|
38
48
|
"search": search,
|
|
39
|
-
"results": search.queryset(),
|
|
49
|
+
"results": search.queryset()[:50],
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
|
|
@@ -46,7 +56,9 @@ class Logs(SearchView):
|
|
|
46
56
|
class search_class(Search):
|
|
47
57
|
timeframe = DateRange(field_name="timestamp")
|
|
48
58
|
name = MultiFacet()
|
|
49
|
-
level = MultiFacet()
|
|
59
|
+
level = MultiFacet(choice_label=level_name)
|
|
60
|
+
environment = MultiFacet()
|
|
61
|
+
node__name = MultiFacet()
|
|
50
62
|
|
|
51
63
|
def get_queryset(self) -> QuerySet:
|
|
52
64
|
return self.site.logs.select_related("context")
|
|
@@ -59,6 +71,8 @@ class Errors(SearchView):
|
|
|
59
71
|
timeframe = DateRange(field_name="timestamp")
|
|
60
72
|
kind = MultiFacet()
|
|
61
73
|
module = MultiFacet()
|
|
74
|
+
environment = MultiFacet()
|
|
75
|
+
node__name = MultiFacet()
|
|
62
76
|
|
|
63
77
|
def get_queryset(self) -> QuerySet:
|
|
64
78
|
return self.site.errors.select_related("context")
|
|
@@ -70,6 +84,8 @@ class Requests(SearchView):
|
|
|
70
84
|
class search_class(Search):
|
|
71
85
|
timeframe = DateRange(field_name="timestamp")
|
|
72
86
|
status = MultiFacet()
|
|
87
|
+
environment = MultiFacet()
|
|
88
|
+
node__name = MultiFacet()
|
|
73
89
|
|
|
74
90
|
def get_queryset(self) -> QuerySet:
|
|
75
91
|
return self.site.requests.select_related("context")
|
|
@@ -82,6 +98,8 @@ class Queries(SearchView):
|
|
|
82
98
|
timeframe = DateRange(field_name="timestamp")
|
|
83
99
|
type = MultiFacet(field_name="command")
|
|
84
100
|
db = MultiFacet()
|
|
101
|
+
environment = MultiFacet()
|
|
102
|
+
node__name = MultiFacet()
|
|
85
103
|
|
|
86
104
|
def get_queryset(self) -> QuerySet:
|
|
87
105
|
return self.site.queries.select_related("context")
|
|
@@ -93,6 +111,8 @@ class Metrics(SearchView):
|
|
|
93
111
|
class search_class(Search):
|
|
94
112
|
timeframe = DateRange(field_name="timestamp")
|
|
95
113
|
name = MultiFacet()
|
|
114
|
+
environment = MultiFacet()
|
|
115
|
+
node__name = MultiFacet()
|
|
96
116
|
|
|
97
117
|
def get_queryset(self) -> QuerySet:
|
|
98
118
|
return self.site.metrics.select_related("context")
|
|
@@ -109,4 +129,37 @@ class Details(SiteView):
|
|
|
109
129
|
obj = ct.get_object_for_this_type(pk=self.kwargs["pk"])
|
|
110
130
|
return {
|
|
111
131
|
"object": obj,
|
|
132
|
+
"context": obj.context,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class NodeDetails(SiteView):
|
|
137
|
+
template_name = "site/details/node.html"
|
|
138
|
+
|
|
139
|
+
def get_context(self):
|
|
140
|
+
node = get_object_or_404(self.site.nodes, pk=self.kwargs["pk"])
|
|
141
|
+
return {
|
|
142
|
+
"node": node,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class NodeEnvironments(SiteView):
|
|
147
|
+
template_name = "site/details/node_environments.html"
|
|
148
|
+
|
|
149
|
+
def get_context(self):
|
|
150
|
+
return {
|
|
151
|
+
"name": self.kwargs["name"],
|
|
152
|
+
"nodes": self.site.nodes.filter(name__iexact=self.kwargs["name"]),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class EnvironmentNodes(SiteView):
|
|
157
|
+
template_name = "site/details/environment_nodes.html"
|
|
158
|
+
|
|
159
|
+
def get_context(self):
|
|
160
|
+
return {
|
|
161
|
+
"environment": self.kwargs["environment"],
|
|
162
|
+
"nodes": self.site.nodes.filter(
|
|
163
|
+
environment__iexact=self.kwargs["environment"]
|
|
164
|
+
),
|
|
112
165
|
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
from typing import Any, ClassVar
|
|
2
|
-
|
|
3
|
-
from django.db.models import Count, QuerySet
|
|
4
|
-
|
|
5
|
-
from .base import SearchField, StringValues
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Choice:
|
|
9
|
-
id: str
|
|
10
|
-
value: str
|
|
11
|
-
label: str
|
|
12
|
-
count: int
|
|
13
|
-
|
|
14
|
-
def __init__(self, field: SearchField, value: Any):
|
|
15
|
-
self.value = self.stringify(value)
|
|
16
|
-
self.label = self.value
|
|
17
|
-
self.id = field.id + "_" + self.value.replace(" ", "")
|
|
18
|
-
self.count = 0
|
|
19
|
-
|
|
20
|
-
@classmethod
|
|
21
|
-
def stringify(cls, value):
|
|
22
|
-
return str(value)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class Facet(SearchField):
|
|
26
|
-
choice_class: ClassVar[type[Choice]] = Choice
|
|
27
|
-
|
|
28
|
-
def get_choices(self, queryset, with_counts=True):
|
|
29
|
-
choices = [
|
|
30
|
-
self.choice_class(self, c)
|
|
31
|
-
for c in queryset.order_by(self.field_name)
|
|
32
|
-
.values_list(self.field_name, flat=True)
|
|
33
|
-
.distinct(self.field_name)
|
|
34
|
-
]
|
|
35
|
-
if with_counts:
|
|
36
|
-
counts = {
|
|
37
|
-
self.choice_class.stringify(r[self.field_name]): r["num"]
|
|
38
|
-
for r in queryset.values(self.field_name).annotate(num=Count("*"))
|
|
39
|
-
}
|
|
40
|
-
for c in choices:
|
|
41
|
-
c.count = counts.get(c.value, 0)
|
|
42
|
-
return choices
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
class MultiFacet(Facet):
|
|
46
|
-
template_name = "search/multifacet.html"
|
|
47
|
-
|
|
48
|
-
def apply(self, queryset: QuerySet, field_data: StringValues) -> QuerySet:
|
|
49
|
-
filters = {}
|
|
50
|
-
if selected := field_data.get("s", []):
|
|
51
|
-
filters[f"{self.field_name}__in"] = selected
|
|
52
|
-
return queryset.filter(**filters)
|
|
53
|
-
|
|
54
|
-
def get_context(self, queryset: QuerySet, field_data: StringValues) -> dict:
|
|
55
|
-
return {
|
|
56
|
-
**super().get_context(queryset, field_data),
|
|
57
|
-
"choices": self.get_choices(queryset),
|
|
58
|
-
"selected": field_data.get("s", []),
|
|
59
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{{ object }}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{{ object }}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{{ object }}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<code><pre>{{ object.sql_formatted }}</pre></code>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{% load i18n %}
|
|
2
|
-
|
|
3
|
-
<table class="table table-sm">
|
|
4
|
-
<tbody>
|
|
5
|
-
<tr>
|
|
6
|
-
<th>{% trans "Host" %}</th>
|
|
7
|
-
<td>{{ object.host }}</td>
|
|
8
|
-
</tr>
|
|
9
|
-
<tr>
|
|
10
|
-
<th>{% trans "Node" %}</th>
|
|
11
|
-
<td>{{ object.node }}</td>
|
|
12
|
-
</tr>
|
|
13
|
-
<tr>
|
|
14
|
-
<th>{% trans "Request" %}</th>
|
|
15
|
-
<td>{{ object.method }} {{ object.path }}</td>
|
|
16
|
-
</tr>
|
|
17
|
-
<tr>
|
|
18
|
-
<th>{% trans "Status" %}</th>
|
|
19
|
-
<td>{{ object.http_status }}</td>
|
|
20
|
-
</tr>
|
|
21
|
-
<tr>
|
|
22
|
-
<th>{% trans "IP" %}</th>
|
|
23
|
-
<td>{{ object.ip }}</td>
|
|
24
|
-
</tr>
|
|
25
|
-
<tr>
|
|
26
|
-
<th>{% trans "User" %}</th>
|
|
27
|
-
<td>{{ object.user }}</td>
|
|
28
|
-
</tr>
|
|
29
|
-
</tbody>
|
|
30
|
-
</table>
|
|
31
|
-
|
|
32
|
-
{% if object.headers %}
|
|
33
|
-
<h5>{% trans "Headers" %}</h5>
|
|
34
|
-
<table class="table table-sm">
|
|
35
|
-
<tbody>
|
|
36
|
-
{% for name, value in object.headers.items %}
|
|
37
|
-
<tr>
|
|
38
|
-
<th>{{ name }}</th>
|
|
39
|
-
<td>{{ value }}</th>
|
|
40
|
-
</tr>
|
|
41
|
-
{% endfor %}
|
|
42
|
-
</tbody>
|
|
43
|
-
</table>
|
|
44
|
-
{% endif %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/management/commands/__init__.py
RENAMED
|
File without changes
|
{varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/management/commands/migrateall.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/registration/login.html
RENAMED
|
File without changes
|
{varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/search/daterange.html
RENAMED
|
File without changes
|
{varanus-0.1.0.dev1 → varanus-0.1.0.dev2}/src/varanus/server/templates/search/multifacet.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|