Compare commits

...

4 Commits

Author SHA1 Message Date
wh1te909
1e3ee91cef fix flake 2025-01-24 22:54:23 +00:00
wh1te909
74721b6d61 blacked 2025-01-24 22:43:42 +00:00
wh1te909
2190943faf debugging black 2025-01-24 22:28:15 +00:00
wh1te909
eb80f07b7d monitoring view v2 2025-01-24 22:01:54 +00:00
5 changed files with 117 additions and 5 deletions

View File

@@ -52,7 +52,7 @@ jobs:
- name: Codestyle black - name: Codestyle black
working-directory: api working-directory: api
run: | run: |
black --exclude migrations/ --check tacticalrmm black --exclude migrations/ --check --diff tacticalrmm
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
exit 1 exit 1
fi fi

View File

@@ -1,9 +1,11 @@
import json import json
from functools import wraps
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
# TODO deprecated
def monitoring_view(function): def monitoring_view(function):
def wrap(request, *args, **kwargs): def wrap(request, *args, **kwargs):
if request.method != "POST": if request.method != "POST":
@@ -29,3 +31,25 @@ def monitoring_view(function):
wrap.__doc__ = function.__doc__ wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__ wrap.__name__ = function.__name__
return wrap return wrap
def monitoring_view_v2(function):
@wraps(function)
def wrap(request, *args, **kwargs):
if request.method != "GET":
return HttpResponse("Invalid request type\n", status=400)
http_token = request.META.get("HTTP_X_MON_TOKEN")
if not http_token:
return HttpResponse("Missing X-Mon-Token header\n", status=401)
mon_token = getattr(settings, "MON_TOKEN", "")
if not mon_token:
return HttpResponse("Missing mon token\n", status=401)
if http_token != mon_token:
return HttpResponse("Not authenticated\n", status=401)
return function(request, *args, **kwargs)
return wrap

View File

@@ -20,7 +20,8 @@ urlpatterns = [
path("urlaction/run/test/", views.RunTestURLAction.as_view()), path("urlaction/run/test/", views.RunTestURLAction.as_view()),
path("smstest/", views.TwilioSMSTest.as_view()), path("smstest/", views.TwilioSMSTest.as_view()),
path("clearcache/", views.clear_cache), path("clearcache/", views.clear_cache),
path("status/", views.status), path("status/", views.status), # TODO deprecated
path("v2/status/", views.status_v2),
path("openai/generate/", views.OpenAICodeCompletion.as_view()), path("openai/generate/", views.OpenAICodeCompletion.as_view()),
path("webtermperms/", views.webterm_perms), path("webtermperms/", views.webterm_perms),
] ]

View File

@@ -20,7 +20,7 @@ from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from core.decorators import monitoring_view from core.decorators import monitoring_view, monitoring_view_v2
from core.tasks import sync_mesh_perms_task from core.tasks import sync_mesh_perms_task
from core.utils import ( from core.utils import (
get_core_settings, get_core_settings,
@@ -32,6 +32,7 @@ from core.utils import (
from logs.models import AuditLog from logs.models import AuditLog
from tacticalrmm.constants import AuditActionType, PAStatus from tacticalrmm.constants import AuditActionType, PAStatus
from tacticalrmm.helpers import get_certs, notify_error from tacticalrmm.helpers import get_certs, notify_error
from tacticalrmm.logger import logger
from tacticalrmm.permissions import ( from tacticalrmm.permissions import (
_has_perm_on_agent, _has_perm_on_agent,
_has_perm_on_client, _has_perm_on_client,
@@ -557,6 +558,72 @@ class TwilioSMSTest(APIView):
return Response(msg) return Response(msg)
@csrf_exempt
@monitoring_view_v2
def status_v2(request):
from agents.models import Agent
from clients.models import Client, Site
from tacticalrmm.helpers import get_nats_ports
from tacticalrmm.utils import get_celery_queue_len, localhost_port_is_open
disk_usage: int = round(psutil.disk_usage("/").percent)
mem_usage: int = round(psutil.virtual_memory().percent)
cert_file, _ = get_certs()
cert_bytes = Path(cert_file).read_bytes()
cert = x509.load_pem_x509_certificate(cert_bytes)
delta = cert.not_valid_after_utc - djangotime.now()
redis_url = f"redis://{settings.REDIS_HOST}"
redis_ping = False
with suppress(Exception):
with from_url(redis_url) as conn:
conn.ping()
redis_ping = True
celery_queue_health = "healthy"
try:
queue_len = get_celery_queue_len()
except RuntimeError as e:
queue_len = -1
celery_queue_health = "unhealthy"
logger.error(f"Error getting celery queue length: {e}")
nats_std_port, nats_ws_port = get_nats_ports()
mesh_port = getattr(settings, "MESH_PORT", 4430)
ret = {
"version": settings.TRMM_VERSION,
"latest_agent_version": settings.LATEST_AGENT_VER,
"agent_count": Agent.objects.count(),
"client_count": Client.objects.count(),
"site_count": Site.objects.count(),
"disk_usage_percent": disk_usage,
"mem_usage_percent": mem_usage,
"days_until_cert_expires": delta.days,
"cert_expired": delta.days < 0,
"redis_ping": redis_ping,
"celery_queue_len": queue_len,
"celery_queue_health": celery_queue_health,
"nats_std_ping": localhost_port_is_open(nats_std_port),
"nats_ws_ping": localhost_port_is_open(nats_ws_port),
"mesh_ping": localhost_port_is_open(mesh_port),
"services_running": {
"mesh": sysd_svc_is_running("meshcentral.service"),
"daphne": sysd_svc_is_running("daphne.service"),
"celery": sysd_svc_is_running("celery.service"),
"celerybeat": sysd_svc_is_running("celerybeat.service"),
"redis": sysd_svc_is_running("redis-server.service"),
"nats": sysd_svc_is_running("nats.service"),
"nats-api": sysd_svc_is_running("nats-api.service"),
},
}
return JsonResponse(ret, json_dumps_params={"indent": 2})
# TODO deprecated
@csrf_exempt @csrf_exempt
@monitoring_view @monitoring_view
def status(request): def status(request):

View File

@@ -1,9 +1,10 @@
import json import json
import os import os
import re
import socket
import subprocess import subprocess
import tempfile import tempfile
import time import time
import re
from contextlib import contextmanager from contextlib import contextmanager
from typing import TYPE_CHECKING, List, Literal, Optional, Union from typing import TYPE_CHECKING, List, Literal, Optional, Union
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@@ -21,6 +22,7 @@ from rest_framework.response import Response
from agents.models import Agent from agents.models import Agent
from core.utils import get_core_settings, token_is_valid from core.utils import get_core_settings, token_is_valid
from logs.models import DebugLog from logs.models import DebugLog
from tacticalrmm.celery import app as celery_app
from tacticalrmm.constants import ( from tacticalrmm.constants import (
MONTH_DAYS, MONTH_DAYS,
MONTHS, MONTHS,
@@ -41,8 +43,8 @@ from tacticalrmm.helpers import (
) )
if TYPE_CHECKING: if TYPE_CHECKING:
from clients.models import Client, Site
from alerts.models import Alert from alerts.models import Alert
from clients.models import Client, Site
def generate_winagent_exe( def generate_winagent_exe(
@@ -468,3 +470,21 @@ def runcmd_placeholder_text() -> dict[str, str]:
), ),
} }
return ret return ret
def get_celery_queue_len():
try:
with celery_app.pool.acquire(block=True) as conn:
return conn.default_channel.client.llen("celery")
except Exception as e:
raise RuntimeError(f"Error getting celery queue length: {e}")
def localhost_port_is_open(port):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(1)
s.connect(("127.0.0.1", port))
return True
except (socket.timeout, ConnectionRefusedError):
return False