From aa6ab33cc200fcb1bff8c09354afc6924c51d198 Mon Sep 17 00:00:00 2001 From: wh1te909 Date: Sun, 9 Aug 2020 09:25:57 +0000 Subject: [PATCH] finish agent recovery --- api/tacticalrmm/agents/urls.py | 1 + api/tacticalrmm/agents/views.py | 28 +++++- api/tacticalrmm/tacticalrmm/settings.py | 2 +- web/src/components/AgentTable.vue | 75 ++++++++------- .../modals/agents/AgentRecovery.vue | 92 +++++++++++++++++++ 5 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 web/src/components/modals/agents/AgentRecovery.vue diff --git a/api/tacticalrmm/agents/urls.py b/api/tacticalrmm/agents/urls.py index f14e60c0..74910c7f 100644 --- a/api/tacticalrmm/agents/urls.py +++ b/api/tacticalrmm/agents/urls.py @@ -21,4 +21,5 @@ urlpatterns = [ path("rebootlater/", views.reboot_later), path("installagent/", views.install_agent), path("/ping/", views.ping), + path("recover/", views.recover), ] diff --git a/api/tacticalrmm/agents/views.py b/api/tacticalrmm/agents/views.py index 7dcd8c78..67ccf9b1 100644 --- a/api/tacticalrmm/agents/views.py +++ b/api/tacticalrmm/agents/views.py @@ -4,6 +4,7 @@ import zlib import json import base64 import datetime as dt +from packaging import version as pyver from django.conf import settings from django.shortcuts import get_object_or_404 @@ -17,7 +18,7 @@ from rest_framework.response import Response from rest_framework import status, generics from rest_framework.authentication import BasicAuthentication, TokenAuthentication -from .models import Agent +from .models import Agent, RecoveryAction from winupdate.models import WinUpdatePolicy from clients.models import Client, Site from accounts.models import User @@ -298,3 +299,28 @@ def install_agent(request): resp = {"token": token, "client": client.pk, "site": site.pk} return Response(resp) + + +@api_view(["POST"]) +def recover(request): + agent = get_object_or_404(Agent, pk=request.data["pk"]) + + if pyver.parse(agent.version) <= pyver.parse("0.9.5"): + return notify_error("Only available in agent version greater than 0.9.5") + + if agent.recoveryactions.filter(last_run=None).exists(): + return notify_error( + "A recovery action is currently pending. Please wait for the next agent check-in." + ) + + if request.data["mode"] == "command" and not request.data["cmd"]: + return notify_error("Command is required") + + RecoveryAction( + agent=agent, + mode=request.data["mode"], + command=request.data["cmd"] if request.data["mode"] == "command" else None, + ).save() + + return Response(f"Recovery will be attempted on the agent's next check-in") + diff --git a/api/tacticalrmm/tacticalrmm/settings.py b/api/tacticalrmm/tacticalrmm/settings.py index 65e4ccb3..f0a63025 100644 --- a/api/tacticalrmm/tacticalrmm/settings.py +++ b/api/tacticalrmm/tacticalrmm/settings.py @@ -11,7 +11,7 @@ AUTH_USER_MODEL = "accounts.User" # bump this version everytime vue code is changed # to alert user they need to manually refresh their browser -APP_VER = "0.0.17" +APP_VER = "0.0.18" # https://github.com/wh1te909/salt LATEST_SALT_VER = "1.0.3" diff --git a/web/src/components/AgentTable.vue b/web/src/components/AgentTable.vue index 826b4fde..38c7766c 100644 --- a/web/src/components/AgentTable.vue +++ b/web/src/components/AgentTable.vue @@ -120,7 +120,6 @@ Send Command - @@ -129,7 +128,6 @@ - @@ -160,7 +158,7 @@ - + @@ -168,7 +166,6 @@ Run Checks - @@ -202,7 +199,6 @@ - @@ -210,7 +206,13 @@ Edit Policies - + + + + + Agent Recovery + + @@ -328,6 +330,10 @@ + + + + @@ -341,6 +347,7 @@ import RebootLater from "@/components/modals/agents/RebootLater"; import PendingActions from "@/components/modals/logs/PendingActions"; import PolicyAdd from "@/components/automation/modals/PolicyAdd"; import SendCommand from "@/components/modals/agents/SendCommand"; +import AgentRecovery from "@/components/modals/agents/AgentRecovery"; export default { name: "AgentTable", @@ -350,7 +357,8 @@ export default { RebootLater, PendingActions, PolicyAdd, - SendCommand + SendCommand, + AgentRecovery, }, mixins: [mixins], data() { @@ -358,13 +366,14 @@ export default { pagination: { rowsPerPage: 0, sortBy: "hostname", - descending: false + descending: false, }, showSendCommand: false, showEditAgentModal: false, showRebootLaterModal: false, showPolicyAddModal: false, - policyAddPk: null + showAgentRecovery: false, + policyAddPk: null, }; }, methods: { @@ -378,7 +387,7 @@ export default { }, 500); }, runPatchStatusScan(pk, hostname) { - axios.get(`/winupdate/${pk}/runupdatescan/`).then(r => { + axios.get(`/winupdate/${pk}/runupdatescan/`).then((r) => { this.notifySuccess(`Scan will be run shortly on ${hostname}`); }); }, @@ -386,11 +395,11 @@ export default { this.$q.loading.show(); this.$axios .get(`/winupdate/${pk}/installnow/`) - .then(r => { + .then((r) => { this.$q.loading.hide(); this.notifySuccess(r.data); }) - .catch(e => { + .catch((e) => { this.$q.loading.hide(); this.notifyError(e.response.data, 5000); }); @@ -413,8 +422,8 @@ export default { runChecks(pk) { axios .get(`/checks/runchecks/${pk}/`) - .then(r => this.notifySuccess(`Checks will now be re-run on ${r.data}`)) - .catch(e => this.notifyError("Something went wrong")); + .then((r) => this.notifySuccess(`Checks will now be re-run on ${r.data}`)) + .catch((e) => this.notifyError("Something went wrong")); }, removeAgent(pk, name) { this.$q @@ -423,18 +432,18 @@ export default { prompt: { model: "", type: "text", - isValid: val => val === name + isValid: (val) => val === name, }, cancel: true, ok: { label: "Uninstall", color: "negative" }, persistent: true, - html: true + html: true, }) - .onOk(val => { + .onOk((val) => { const data = { pk: pk }; this.$axios .delete("/agents/uninstall/", { data: data }) - .then(r => { + .then((r) => { this.notifySuccess(r.data); setTimeout(() => { location.reload(); @@ -447,7 +456,7 @@ export default { this.$q.loading.show(); this.$axios .get(`/agents/${pk}/ping/`) - .then(r => { + .then((r) => { this.$q.loading.hide(); if (r.data.status === "offline") { this.$q @@ -458,7 +467,7 @@ export default { If so, the agent will need to be manually uninstalled from the computer.`, cancel: { label: "No", color: "negative" }, ok: { label: "Yes", color: "positive" }, - persistent: true + persistent: true, }) .onOk(() => this.removeAgent(pk, r.data.name)) .onCancel(() => { @@ -470,7 +479,7 @@ export default { this.notifyError("Something went wrong"); } }) - .catch(e => { + .catch((e) => { this.$q.loading.hide(); this.notifyError("Something went wrong"); }); @@ -481,14 +490,14 @@ export default { title: "Are you sure?", message: `Reboot ${hostname} now`, cancel: true, - persistent: true + persistent: true, }) .onOk(() => { const data = { pk: pk, action: "rebootnow" }; - axios.post("/agents/poweraction/", data).then(r => { + axios.post("/agents/poweraction/", data).then((r) => { this.$q.dialog({ title: `Restarting ${hostname}`, - message: `${hostname} will now be restarted` + message: `${hostname} will now be restarted`, }); }); }); @@ -506,19 +515,19 @@ export default { const data = { pk: pk, alertType: category, - action: action + action: action, }; const alertColor = alert_action ? "positive" : "warning"; axios .post("/agents/overdueaction/", data) - .then(r => { + .then((r) => { this.$q.notify({ color: alertColor, icon: "fas fa-check-circle", - message: `Overdue ${category} alerts ${action} on ${r.data}` + message: `Overdue ${category} alerts ${action} on ${r.data}`, }); }) - .catch(e => this.notifyError(e.response.data.error)); + .catch((e) => this.notifyError(e.response.data.error)); }, agentClass(status) { if (status === "offline") { @@ -537,15 +546,15 @@ export default { this.$q.loading.show(); this.$axios .get(`/agents/${pk}/meshcentral/`) - .then(r => { + .then((r) => { this.$q.loading.hide(); openURL(r.data.webrdp); }) - .catch(e => { + .catch((e) => { this.$q.loading.hide(); this.notifyError(e.response.data); }); - } + }, }, computed: { ...mapGetters(["selectedAgentPk"]), @@ -554,8 +563,8 @@ export default { }, agentTableLoading() { return this.$store.state.agentTableLoading; - } - } + }, + }, }; diff --git a/web/src/components/modals/agents/AgentRecovery.vue b/web/src/components/modals/agents/AgentRecovery.vue new file mode 100644 index 00000000..ff3ad846 --- /dev/null +++ b/web/src/components/modals/agents/AgentRecovery.vue @@ -0,0 +1,92 @@ + + + \ No newline at end of file