Skip to content

Commit eb80f07

Browse files
committed
monitoring view v2
1 parent 83b4d8c commit eb80f07

File tree

4 files changed

+116
-4
lines changed

4 files changed

+116
-4
lines changed

api/tacticalrmm/core/decorators.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import json
2+
from functools import wraps
23

34
from django.conf import settings
45
from django.http import HttpResponse
56

67

8+
# TODO deprecated
79
def monitoring_view(function):
810
def wrap(request, *args, **kwargs):
911
if request.method != "POST":
@@ -29,3 +31,25 @@ def wrap(request, *args, **kwargs):
2931
wrap.__doc__ = function.__doc__
3032
wrap.__name__ = function.__name__
3133
return wrap
34+
35+
36+
def monitoring_view_v2(function):
37+
@wraps(function)
38+
def wrap(request, *args, **kwargs):
39+
if request.method != "GET":
40+
return HttpResponse("Invalid request type\n", status=400)
41+
42+
http_token = request.META.get("HTTP_X_MON_TOKEN")
43+
if not http_token:
44+
return HttpResponse("Missing X-Mon-Token header\n", status=401)
45+
46+
mon_token = getattr(settings, "MON_TOKEN", "")
47+
if not mon_token:
48+
return HttpResponse("Missing mon token\n", status=401)
49+
50+
if http_token != mon_token:
51+
return HttpResponse("Not authenticated\n", status=401)
52+
53+
return function(request, *args, **kwargs)
54+
55+
return wrap

api/tacticalrmm/core/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
path("urlaction/run/test/", views.RunTestURLAction.as_view()),
2121
path("smstest/", views.TwilioSMSTest.as_view()),
2222
path("clearcache/", views.clear_cache),
23-
path("status/", views.status),
23+
path("status/", views.status), # TODO deprecated
24+
path("v2/status/", views.status_v2),
2425
path("openai/generate/", views.OpenAICodeCompletion.as_view()),
2526
path("webtermperms/", views.webterm_perms),
2627
]

api/tacticalrmm/core/views.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from rest_framework.response import Response
2121
from rest_framework.views import APIView
2222

23-
from core.decorators import monitoring_view
23+
from core.decorators import monitoring_view, monitoring_view_v2
2424
from core.tasks import sync_mesh_perms_task
2525
from core.utils import (
2626
get_core_settings,
@@ -32,6 +32,7 @@
3232
from logs.models import AuditLog
3333
from tacticalrmm.constants import AuditActionType, PAStatus
3434
from tacticalrmm.helpers import get_certs, notify_error
35+
from tacticalrmm.logger import logger
3536
from tacticalrmm.permissions import (
3637
_has_perm_on_agent,
3738
_has_perm_on_client,
@@ -557,6 +558,72 @@ def post(self, request):
557558
return Response(msg)
558559

559560

561+
@csrf_exempt
562+
@monitoring_view_v2
563+
def status_v2(request):
564+
from agents.models import Agent
565+
from clients.models import Client, Site
566+
from tacticalrmm.helpers import get_nats_ports
567+
from tacticalrmm.utils import get_celery_queue_len, localhost_port_is_open
568+
569+
disk_usage: int = round(psutil.disk_usage("/").percent)
570+
mem_usage: int = round(psutil.virtual_memory().percent)
571+
572+
cert_file, _ = get_certs()
573+
cert_bytes = Path(cert_file).read_bytes()
574+
575+
cert = x509.load_pem_x509_certificate(cert_bytes)
576+
delta = cert.not_valid_after_utc - djangotime.now()
577+
578+
redis_url = f"redis://{settings.REDIS_HOST}"
579+
redis_ping = False
580+
with suppress(Exception):
581+
with from_url(redis_url) as conn:
582+
conn.ping()
583+
redis_ping = True
584+
585+
celery_queue_health = "healthy"
586+
try:
587+
queue_len = get_celery_queue_len()
588+
except RuntimeError as e:
589+
queue_len = -1
590+
celery_queue_health = "unhealthy"
591+
logger.error(f"Error getting celery queue length: {e}")
592+
593+
nats_std_port, nats_ws_port = get_nats_ports()
594+
mesh_port = getattr(settings, "MESH_PORT", 4430)
595+
596+
ret = {
597+
"version": settings.TRMM_VERSION,
598+
"latest_agent_version": settings.LATEST_AGENT_VER,
599+
"agent_count": Agent.objects.count(),
600+
"client_count": Client.objects.count(),
601+
"site_count": Site.objects.count(),
602+
"disk_usage_percent": disk_usage,
603+
"mem_usage_percent": mem_usage,
604+
"days_until_cert_expires": delta.days,
605+
"cert_expired": delta.days < 0,
606+
"redis_ping": redis_ping,
607+
"celery_queue_len": queue_len,
608+
"celery_queue_health": celery_queue_health,
609+
"nats_std_ping": localhost_port_is_open(nats_std_port),
610+
"nats_ws_ping": localhost_port_is_open(nats_ws_port),
611+
"mesh_ping": localhost_port_is_open(mesh_port),
612+
"services_running": {
613+
"mesh": sysd_svc_is_running("meshcentral.service"),
614+
"daphne": sysd_svc_is_running("daphne.service"),
615+
"celery": sysd_svc_is_running("celery.service"),
616+
"celerybeat": sysd_svc_is_running("celerybeat.service"),
617+
"redis": sysd_svc_is_running("redis-server.service"),
618+
"nats": sysd_svc_is_running("nats.service"),
619+
"nats-api": sysd_svc_is_running("nats-api.service"),
620+
}
621+
}
622+
623+
return JsonResponse(ret, json_dumps_params={"indent": 2})
624+
625+
626+
## TODO deprecated
560627
@csrf_exempt
561628
@monitoring_view
562629
def status(request):

api/tacticalrmm/tacticalrmm/utils.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import json
22
import os
3+
import re
4+
import socket
35
import subprocess
46
import tempfile
57
import time
6-
import re
78
from contextlib import contextmanager
89
from typing import TYPE_CHECKING, List, Literal, Optional, Union
910
from zoneinfo import ZoneInfo
@@ -21,6 +22,7 @@
2122
from agents.models import Agent
2223
from core.utils import get_core_settings, token_is_valid
2324
from logs.models import DebugLog
25+
from tacticalrmm.celery import app as celery_app
2426
from tacticalrmm.constants import (
2527
MONTH_DAYS,
2628
MONTHS,
@@ -41,8 +43,8 @@
4143
)
4244

4345
if TYPE_CHECKING:
44-
from clients.models import Client, Site
4546
from alerts.models import Alert
47+
from clients.models import Client, Site
4648

4749

4850
def generate_winagent_exe(
@@ -468,3 +470,21 @@ def runcmd_placeholder_text() -> dict[str, str]:
468470
),
469471
}
470472
return ret
473+
474+
475+
def get_celery_queue_len():
476+
try:
477+
with celery_app.pool.acquire(block=True) as conn:
478+
return conn.default_channel.client.llen("celery")
479+
except Exception as e:
480+
raise RuntimeError(f"Error getting celery queue length: {e}")
481+
482+
483+
def localhost_port_is_open(port):
484+
try:
485+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
486+
s.settimeout(1)
487+
s.connect(("127.0.0.1", port))
488+
return True
489+
except (socket.timeout, ConnectionRefusedError):
490+
return False

0 commit comments

Comments
 (0)