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:
parent
bcc2915fad
commit
45906427ae
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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/;
|
||||
|
|
|
@ -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/ .
|
||||
|
|
|
@ -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
|
||||
|
|
18
install.sh
18
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/;
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue