move debuglog to logs app, create new dirs to store logs/exes in, add new view to upload a new meshagent

This commit is contained in:
wh1te909 2020-06-01 08:16:00 +00:00
parent bcc2915fad
commit 45906427ae
16 changed files with 162 additions and 93 deletions

View File

@ -8,8 +8,6 @@ urlpatterns = [
path("token/", views.create_auth_token),
path("acceptsaltkey/", views.accept_salt_key),
path("deleteagent/", views.delete_agent),
path("getrmmlog/<mode>/<hostname>/<order>/", 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),

View File

@ -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

View File

@ -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)

View File

@ -5,4 +5,6 @@ urlpatterns = [
path("<int:pk>/pendingactions/", views.agent_pending_actions),
path("allpendingactions/", views.all_pending_actions),
path("cancelpendingaction/", views.cancel_pending_action),
path("debuglog/<mode>/<hostname>/<order>/", views.debug_log),
path("downloadlog/", views.download_log),
]

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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/;

View File

@ -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/ .

View File

@ -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

View File

@ -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/;

View File

@ -11,6 +11,9 @@
<q-item clickable v-close-popup @click="showAddSiteModal = true">
<q-item-section>Add Site</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="showUploadMesh = true">
<q-item-section>Upload MeshAgent</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="getLog">
<q-item-section>Debug Log</q-item-section>
</q-item>
@ -121,6 +124,10 @@
<AutomationManager @close="showAutomationManager = false" />
</q-dialog>
</div>
<!-- Upload new mesh agent -->
<q-dialog v-model="showUploadMesh">
<UploadMesh @close="showUploadMesh = false" />
</q-dialog>
</q-bar>
</div>
</template>
@ -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: {

View File

@ -0,0 +1,61 @@
<template>
<q-card style="min-width: 40vw">
<q-card-section class="row items-center">
<div class="text-h6">Upload New Mesh Agent</div>
</q-card-section>
<q-form @submit.prevent="upload">
<q-card-section>
<div class="row">
<q-file
v-model="meshagent"
:rules="[ val => !!val || '*Required' ]"
label="Upload MeshAgent"
stack-label
filled
counter
accept=".exe"
>
<template v-slot:prepend>
<q-icon name="attach_file" />
</template>
</q-file>
</div>
</q-card-section>
<q-card-actions align="center">
<q-btn label="Upload" color="primary" class="full-width" type="submit" />
</q-card-actions>
</q-form>
</q-card>
</template>
<script>
import axios from "axios";
import mixins from "@/mixins/mixins";
export default {
name: "UploadMesh",
mixins: [mixins],
data() {
return {
meshagent: null
};
},
methods: {
upload() {
let formData = new FormData();
formData.append("meshagent", this.meshagent);
axios
.put("/core/uploadmesh/", formData)
.then(() => {
this.$q.loading.hide();
this.$emit("close");
this.notifySuccess("Uploaded successfully!");
})
.catch(e => {
this.$q.loading.hide();
this.notifyError("Unable to upload");
});
}
}
};
</script>

View File

@ -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");