diff --git a/api/tacticalrmm/api/urls.py b/api/tacticalrmm/api/urls.py index cd18db8e..e8c12166 100644 --- a/api/tacticalrmm/api/urls.py +++ b/api/tacticalrmm/api/urls.py @@ -8,8 +8,6 @@ urlpatterns = [ path("token/", views.create_auth_token), path("acceptsaltkey/", views.accept_salt_key), path("deleteagent/", views.delete_agent), - path("getrmmlog////", views.get_log), - path("downloadrmmlog/", views.download_log), path("getmeshexe/", views.get_mesh_exe), path("triggerpatchscan/", views.trigger_patch_scan), path("firstinstall/", views.on_agent_first_install), diff --git a/api/tacticalrmm/api/views.py b/api/tacticalrmm/api/views.py index c6c8d95e..3d9e8e0f 100644 --- a/api/tacticalrmm/api/views.py +++ b/api/tacticalrmm/api/views.py @@ -1,5 +1,5 @@ import requests -from subprocess import run, PIPE + import os from time import sleep from loguru import logger @@ -14,10 +14,7 @@ from rest_framework.response import Response from rest_framework.authtoken.models import Token from rest_framework.views import APIView from rest_framework import status -from rest_framework.authentication import ( - BasicAuthentication, - TokenAuthentication, -) +from rest_framework.authentication import TokenAuthentication from rest_framework.permissions import IsAuthenticated from rest_framework.decorators import ( api_view, @@ -41,7 +38,6 @@ from clients.models import Client, Site from winupdate.tasks import check_for_updates_task from agents.serializers import ( - AgentHostnameSerializer, AgentSerializer, WinAgentSerializer, ) @@ -52,7 +48,6 @@ from software.tasks import install_chocolatey, get_installed_software logger.configure(**settings.LOG_CONFIG) - @api_view(["PATCH"]) @authentication_classes((TokenAuthentication,)) @permission_classes((IsAuthenticated,)) @@ -69,60 +64,9 @@ def trigger_patch_scan(request): return Response("ok") -@api_view() -def download_log(request): - log_file = settings.LOG_CONFIG["handlers"][0]["sink"] - if settings.DEBUG: - with open(log_file, "rb") as f: - response = HttpResponse(f.read(), content_type="text/plain") - response["Content-Disposition"] = "attachment; filename=debug.log" - return response - else: - response = HttpResponse() - response["Content-Disposition"] = "attachment; filename=debug.log" - response["X-Accel-Redirect"] = "/protectedlogs/debug.log" - return response - - -@api_view() -def get_log(request, mode, hostname, order): - log_file = settings.LOG_CONFIG["handlers"][0]["sink"] - - agents = Agent.objects.all() - agent_hostnames = AgentHostnameSerializer(agents, many=True) - - switch_mode = { - "info": "INFO", - "critical": "CRITICAL", - "error": "ERROR", - "warning": "WARNING", - } - level = switch_mode.get(mode, "INFO") - - if hostname == "all" and order == "latest": - cmd = f"grep -h {level} {log_file} | tac" - elif hostname == "all" and order == "oldest": - cmd = f"grep -h {level} {log_file}" - elif hostname != "all" and order == "latest": - cmd = f"grep {hostname} {log_file} | grep -h {level} | tac" - elif hostname != "all" and order == "oldest": - cmd = f"grep {hostname} {log_file} | grep -h {level}" - else: - return Response("error", status=status.HTTP_400_BAD_REQUEST) - - contents = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True, shell=True) - - if not contents.stdout: - resp = f"Hooray! No {mode} logs!" - else: - resp = contents.stdout - - return Response({"log": resp, "agents": agent_hostnames.data}) - - @api_view(["POST"]) def get_mesh_exe(request): - mesh_exe = os.path.join(settings.BASE_DIR, "tacticalrmm/downloads/meshagent.exe") + mesh_exe = os.path.join(settings.EXE_DIR, "meshagent.exe") if not os.path.exists(mesh_exe): return Response("error", status=status.HTTP_400_BAD_REQUEST) if settings.DEBUG: @@ -135,7 +79,7 @@ def get_mesh_exe(request): else: response = HttpResponse() response["Content-Disposition"] = "attachment; filename=meshagent.exe" - response["X-Accel-Redirect"] = "/protected/meshagent.exe" + response["X-Accel-Redirect"] = "/private/exe/meshagent.exe" return response diff --git a/api/tacticalrmm/core/views.py b/api/tacticalrmm/core/views.py index 29b4d2cb..9cfc6189 100644 --- a/api/tacticalrmm/core/views.py +++ b/api/tacticalrmm/core/views.py @@ -21,9 +21,7 @@ class UploadMeshAgent(APIView): raise ParseError("Empty content") f = request.data["meshagent"] - mesh_exe = os.path.join( - settings.BASE_DIR, "tacticalrmm/downloads/meshagent.exe" - ) + mesh_exe = os.path.join(settings.EXE_DIR, "meshagent.exe") with open(mesh_exe, "wb+") as j: for chunk in f.chunks(): j.write(chunk) diff --git a/api/tacticalrmm/logs/urls.py b/api/tacticalrmm/logs/urls.py index 7a47a9b8..8045160a 100644 --- a/api/tacticalrmm/logs/urls.py +++ b/api/tacticalrmm/logs/urls.py @@ -5,4 +5,6 @@ urlpatterns = [ path("/pendingactions/", views.agent_pending_actions), path("allpendingactions/", views.all_pending_actions), path("cancelpendingaction/", views.cancel_pending_action), + path("debuglog////", views.debug_log), + path("downloadlog/", views.download_log), ] diff --git a/api/tacticalrmm/logs/views.py b/api/tacticalrmm/logs/views.py index 3b7f6855..8f6fa070 100644 --- a/api/tacticalrmm/logs/views.py +++ b/api/tacticalrmm/logs/views.py @@ -1,11 +1,17 @@ +import subprocess + +from django.conf import settings from django.shortcuts import get_object_or_404 +from django.http import HttpResponse from rest_framework.response import Response from rest_framework import status from rest_framework.decorators import api_view from .models import PendingAction +from agents.models import Agent from .serializers import PendingActionSerializer +from agents.serializers import AgentHostnameSerializer from .tasks import cancel_pending_action_task @@ -30,3 +36,60 @@ def cancel_pending_action(request): return Response( f"{action.agent.hostname}: {action.description} will be cancelled shortly" ) + + +@api_view() +def debug_log(request, mode, hostname, order): + log_file = settings.LOG_CONFIG["handlers"][0]["sink"] + + agents = Agent.objects.all() + agent_hostnames = AgentHostnameSerializer(agents, many=True) + + switch_mode = { + "info": "INFO", + "critical": "CRITICAL", + "error": "ERROR", + "warning": "WARNING", + } + level = switch_mode.get(mode, "INFO") + + if hostname == "all" and order == "latest": + cmd = f"grep -h {level} {log_file} | tac" + elif hostname == "all" and order == "oldest": + cmd = f"grep -h {level} {log_file}" + elif hostname != "all" and order == "latest": + cmd = f"grep {hostname} {log_file} | grep -h {level} | tac" + elif hostname != "all" and order == "oldest": + cmd = f"grep {hostname} {log_file} | grep -h {level}" + else: + return Response("error", status=status.HTTP_400_BAD_REQUEST) + + contents = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + shell=True, + ) + + if not contents.stdout: + resp = f"No {mode} logs" + else: + resp = contents.stdout + + return Response({"log": resp, "agents": agent_hostnames.data}) + + +@api_view() +def download_log(request): + log_file = settings.LOG_CONFIG["handlers"][0]["sink"] + if settings.DEBUG: + with open(log_file, "rb") as f: + response = HttpResponse(f.read(), content_type="text/plain") + response["Content-Disposition"] = "attachment; filename=debug.log" + return response + else: + response = HttpResponse() + response["Content-Disposition"] = "attachment; filename=debug.log" + response["X-Accel-Redirect"] = "/private/log/debug.log" + return response diff --git a/api/tacticalrmm/scripts/views.py b/api/tacticalrmm/scripts/views.py index ea5417e0..4a7c048a 100644 --- a/api/tacticalrmm/scripts/views.py +++ b/api/tacticalrmm/scripts/views.py @@ -107,5 +107,5 @@ def download(request, pk): else: response = HttpResponse() response["Content-Disposition"] = f"attachment; filename={script.filename}" - response["X-Accel-Redirect"] = f"/protectedscripts/{script.filename}" + response["X-Accel-Redirect"] = f"/saltscripts/{script.filename}" return response diff --git a/api/tacticalrmm/log/.gitkeep b/api/tacticalrmm/tacticalrmm/private/exe/.gitkeep similarity index 100% rename from api/tacticalrmm/log/.gitkeep rename to api/tacticalrmm/tacticalrmm/private/exe/.gitkeep diff --git a/api/tacticalrmm/tacticalrmm/downloads/.gitkeep b/api/tacticalrmm/tacticalrmm/private/log/.gitkeep similarity index 100% rename from api/tacticalrmm/tacticalrmm/downloads/.gitkeep rename to api/tacticalrmm/tacticalrmm/private/log/.gitkeep diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index fa769da3..c3dd0029 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -3,6 +3,10 @@ from datetime import timedelta BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +LOG_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/log") + +EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe") + AUTH_USER_MODEL = "accounts.User" INSTALLED_APPS = [ @@ -98,7 +102,7 @@ STATICFILES_DIRS = [os.path.join(BASE_DIR, "tacticalrmm/static/")] LOG_CONFIG = { - "handlers": [{"sink": os.path.join(BASE_DIR, "logs/debug.log"), "serialize": False}] + "handlers": [{"sink": os.path.join(LOG_DIR, "debug.log"), "serialize": False}] } try: diff --git a/docker/api/api.conf b/docker/api/api.conf index 76a41cb2..2a1660e3 100644 --- a/docker/api/api.conf +++ b/docker/api/api.conf @@ -34,19 +34,13 @@ http { root /app; } - location /protected/ { + location /private/ { internal; add_header "Access-Control-Allow-Origin" "https://${APP_HOST}"; - alias /app/tacticalrmm/downloads/; + alias /app/tacticalrmm/private/; } - location /protectedlogs/ { - internal; - add_header "Access-Control-Allow-Origin" "https://${APP_HOST}"; - alias /app/log/; - } - - location /protectedscripts/ { + location /saltscripts/ { internal; add_header "Access-Control-Allow-Origin" "https://${APP_HOST}"; alias /srv/salt/scripts/userdefined/; diff --git a/docker/api/dockerfile b/docker/api/dockerfile index 29c0a0d0..e6e9d0be 100644 --- a/docker/api/dockerfile +++ b/docker/api/dockerfile @@ -22,6 +22,7 @@ EXPOSE 80 RUN apt-get update && apt-get install -y gettext-base COPY ./api/tacticalrmm/requirements.txt . +RUN pip install --upgrade pip RUN pip install --no-cache-dir --upgrade setuptools wheel RUN pip install --no-cache-dir -r requirements.txt COPY ./api/tacticalrmm/ . diff --git a/docker/api/uwsgi.ini b/docker/api/uwsgi.ini index bcf81a38..e9e26e1b 100644 --- a/docker/api/uwsgi.ini +++ b/docker/api/uwsgi.ini @@ -1,6 +1,6 @@ [uwsgi] -logto = /app/log/uwsgi.log +logto = /app/tacticalrmm/private/log/uwsgi.log chdir = /app wsgi-file = tacticalrmm/wsgi.py master = true diff --git a/install.sh b/install.sh index edb2945c..39026877 100755 --- a/install.sh +++ b/install.sh @@ -253,7 +253,7 @@ read -n 1 -s -r -p "Press any key to continue..." uwsgini="$(cat << EOF [uwsgi] -logto = /home/${USER}/rmm/api/tacticalrmm/log/uwsgi.log +logto = /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/private/log/uwsgi.log chdir = /home/${USER}/rmm/api/tacticalrmm module = tacticalrmm.wsgi home = /home/${USER}/rmm/api/env @@ -312,8 +312,8 @@ server { listen 443 ssl; server_name ${rmmdomain}; client_max_body_size 300M; - access_log /home/${USER}/rmm/api/tacticalrmm/log/rmm-access.log; - error_log /home/${USER}/rmm/api/tacticalrmm/log/rmm-error.log; + access_log /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/private/log/access.log; + error_log /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/private/log/error.log; ssl_certificate /etc/letsencrypt/live/${rmmdomain}/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/${rmmdomain}/privkey.pem; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; @@ -322,19 +322,13 @@ server { root /home/${USER}/rmm/api/tacticalrmm; } - location /protected/ { + location /private/ { internal; add_header "Access-Control-Allow-Origin" "https://${frontenddomain}"; - alias /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/downloads/; + alias /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/private/; } - location /protectedlogs/ { - internal; - add_header "Access-Control-Allow-Origin" "https://${frontenddomain}"; - alias /home/${USER}/rmm/api/tacticalrmm/log/; - } - - location /protectedscripts/ { + location /saltscripts/ { internal; add_header "Access-Control-Allow-Origin" "https://${frontenddomain}"; alias /srv/salt/scripts/userdefined/; diff --git a/web/src/components/FileBar.vue b/web/src/components/FileBar.vue index c134d9bc..4d6bd68a 100644 --- a/web/src/components/FileBar.vue +++ b/web/src/components/FileBar.vue @@ -11,6 +11,9 @@ Add Site + + Upload MeshAgent + Debug Log @@ -121,6 +124,10 @@ + + + + @@ -136,6 +143,7 @@ import ScriptManager from "@/components/ScriptManager"; import EditCoreSettings from "@/components/modals/coresettings/EditCoreSettings"; import AutomationManager from "@/components/automation/AutomationManager"; import InstallAgent from "@/components/modals/agents/InstallAgent"; +import UploadMesh from "@/components/modals/core/UploadMesh"; export default { name: "FileBar", @@ -149,7 +157,8 @@ export default { ScriptManager, EditCoreSettings, AutomationManager, - InstallAgent + InstallAgent, + UploadMesh }, props: ["clients"], data() { @@ -161,7 +170,8 @@ export default { showUpdateAgentsModal: false, showEditCoreSettingsModal: false, showAutomationManager: false, - showInstallAgent: false + showInstallAgent: false, + showUploadMesh: false }; }, methods: { diff --git a/web/src/components/modals/core/UploadMesh.vue b/web/src/components/modals/core/UploadMesh.vue new file mode 100644 index 00000000..dd336c73 --- /dev/null +++ b/web/src/components/modals/core/UploadMesh.vue @@ -0,0 +1,61 @@ + + + \ No newline at end of file diff --git a/web/src/components/modals/logs/LogModal.vue b/web/src/components/modals/logs/LogModal.vue index 79c76c75..932a6695 100644 --- a/web/src/components/modals/logs/LogModal.vue +++ b/web/src/components/modals/logs/LogModal.vue @@ -94,7 +94,7 @@ export default { methods: { downloadLog() { axios - .get("/api/v1/downloadrmmlog/", { responseType: "blob" }) + .get("/logs/downloadlog/", { responseType: "blob" }) .then(({ data }) => { const blob = new Blob([data], { type: "text/plain" }); let link = document.createElement("a"); @@ -105,7 +105,7 @@ export default { .catch(error => console.error(error)); }, getLog() { - axios.get(`/api/v1/getrmmlog/${this.loglevel}/${this.agent}/${this.order}/`).then(r => { + axios.get(`/logs/debuglog/${this.loglevel}/${this.agent}/${this.order}/`).then(r => { this.logContent = r.data.log; this.agents = r.data.agents.map(k => k.hostname); this.agents.unshift("all");