pending actions refactor

This commit is contained in:
wh1te909 2021-03-01 20:40:46 +00:00
parent 9ade78b703
commit dbd1f0d4f9
4 changed files with 111 additions and 80 deletions

View File

@ -3,10 +3,9 @@ from unittest.mock import patch
from model_bakery import baker, seq
from logs.models import PendingAction
from tacticalrmm.test import TacticalTestCase
from .serializers import PendingActionSerializer
class TestAuditViews(TacticalTestCase):
def setUp(self):
@ -177,63 +176,94 @@ class TestAuditViews(TacticalTestCase):
self.check_not_authenticated("post", url)
def test_agent_pending_actions(self):
agent = baker.make_recipe("agents.agent")
pending_actions = baker.make(
def test_get_pending_actions(self):
url = "/logs/pendingactions/"
agent1 = baker.make_recipe("agents.online_agent")
agent2 = baker.make_recipe("agents.online_agent")
baker.make(
"logs.PendingAction",
agent=agent,
_quantity=6,
agent=agent1,
action_type="chocoinstall",
details={"name": "googlechrome", "output": None, "installed": False},
_quantity=12,
)
baker.make(
"logs.PendingAction",
agent=agent2,
action_type="chocoinstall",
status="completed",
details={"name": "adobereader", "output": None, "installed": False},
_quantity=14,
)
url = f"/logs/{agent.pk}/pendingactions/"
resp = self.client.get(url, format="json")
serializer = PendingActionSerializer(pending_actions, many=True)
data = {"showCompleted": False}
r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.data["actions"]), 12) # type: ignore
self.assertEqual(r.data["completed_count"], 14) # type: ignore
self.assertEqual(resp.status_code, 200)
self.assertEqual(len(resp.data), 6)
self.assertEqual(resp.data, serializer.data)
PendingAction.objects.filter(action_type="chocoinstall").update(
status="completed"
)
data = {"showCompleted": True}
r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.data["actions"]), 26) # type: ignore
self.assertEqual(r.data["completed_count"], 26) # type: ignore
self.check_not_authenticated("get", url)
data = {"showCompleted": True, "agentPK": agent1.pk}
r = self.client.patch(url, data, format="json")
self.assertEqual(r.status_code, 200)
self.assertEqual(len(r.data["actions"]), 12) # type: ignore
self.assertEqual(r.data["completed_count"], 26) # type: ignore
def test_all_pending_actions(self):
url = "/logs/allpendingactions/"
agent = baker.make_recipe("agents.agent")
pending_actions = baker.make("logs.PendingAction", agent=agent, _quantity=6)
resp = self.client.get(url, format="json")
serializer = PendingActionSerializer(pending_actions, many=True)
self.assertEqual(resp.status_code, 200)
self.assertEqual(len(resp.data), 6)
self.assertEqual(resp.data, serializer.data)
self.check_not_authenticated("get", url)
self.check_not_authenticated("patch", url)
@patch("agents.models.Agent.nats_cmd")
def test_cancel_pending_action(self, nats_cmd):
url = "/logs/cancelpendingaction/"
# TODO fix this TypeError: Object of type coroutine is not JSON serializable
""" agent = baker.make("agents.Agent", version="1.1.1")
pending_action = baker.make(
nats_cmd.return_value = "ok"
url = "/logs/pendingactions/"
agent = baker.make_recipe("agents.online_agent")
action = baker.make(
"logs.PendingAction",
agent=agent,
action_type="schedreboot",
details={
"time": "2021-01-13 18:20:00",
"taskname": "TacticalRMM_SchedReboot_wYzCCDVXlc",
},
)
data = {"pk": pending_action.id}
resp = self.client.delete(url, data, format="json")
self.assertEqual(resp.status_code, 200)
data = {"pk": action.pk} # type: ignore
r = self.client.delete(url, data, format="json")
self.assertEqual(r.status_code, 200)
nats_data = {
"func": "delschedtask",
"schedtaskpayload": {"name": "TacticalRMM_SchedReboot_wYzCCDVXlc"},
}
nats_cmd.assert_called_with(nats_data, timeout=10)
# try request again and it should fail since pending action doesn't exist
resp = self.client.delete(url, data, format="json")
self.assertEqual(resp.status_code, 404) """
# try request again and it should 404 since pending action doesn't exist
r = self.client.delete(url, data, format="json")
self.assertEqual(r.status_code, 404)
nats_cmd.reset_mock()
action2 = baker.make(
"logs.PendingAction",
agent=agent,
action_type="schedreboot",
details={
"time": "2021-01-13 18:20:00",
"taskname": "TacticalRMM_SchedReboot_wYzCCDVXlc",
},
)
data = {"pk": action2.pk} # type: ignore
nats_cmd.return_value = "error deleting sched task"
r = self.client.delete(url, data, format="json")
self.assertEqual(r.status_code, 400)
self.assertEqual(r.data, "error deleting sched task") # type: ignore
self.check_not_authenticated("delete", url)

View File

@ -3,11 +3,9 @@ from django.urls import path
from . import views
urlpatterns = [
path("pendingactions/", views.PendingActions.as_view()),
path("auditlogs/", views.GetAuditLogs.as_view()),
path("auditlogs/optionsfilter/", views.FilterOptionsAuditLog.as_view()),
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

@ -106,34 +106,38 @@ class FilterOptionsAuditLog(APIView):
return Response("error", status=status.HTTP_400_BAD_REQUEST)
@api_view()
def agent_pending_actions(request, pk):
action = PendingAction.objects.filter(agent__pk=pk)
return Response(PendingActionSerializer(action, many=True).data)
class PendingActions(APIView):
def patch(self, request):
status_filter = "completed" if request.data["showCompleted"] else "pending"
completed = PendingAction.objects.filter(status="completed").count()
if "agentPK" in request.data.keys():
actions = PendingAction.objects.filter(
agent__pk=request.data["agentPK"], status=status_filter
)
else:
actions = PendingAction.objects.filter(status=status_filter).select_related(
"agent"
)
@api_view()
def all_pending_actions(request):
actions = PendingAction.objects.all().select_related("agent")
return Response(PendingActionSerializer(actions, many=True).data)
ret = {
"actions": PendingActionSerializer(actions, many=True).data,
"completed_count": completed,
}
return Response(ret)
def delete(self, request):
action = get_object_or_404(PendingAction, pk=request.data["pk"])
nats_data = {
"func": "delschedtask",
"schedtaskpayload": {"name": action.details["taskname"]},
}
r = asyncio.run(action.agent.nats_cmd(nats_data, timeout=10))
if r != "ok":
return notify_error(r)
@api_view(["DELETE"])
def cancel_pending_action(request):
action = get_object_or_404(PendingAction, pk=request.data["pk"])
if not action.agent.has_gotasks:
return notify_error("Requires agent version 1.1.1 or greater")
nats_data = {
"func": "delschedtask",
"schedtaskpayload": {"name": action.details["taskname"]},
}
r = asyncio.run(action.agent.nats_cmd(nats_data, timeout=10))
if r != "ok":
return notify_error(r)
action.delete()
return Response(f"{action.agent.hostname}: {action.description} was cancelled")
action.delete()
return Response(f"{action.agent.hostname}: {action.description} was cancelled")
@api_view()

View File

@ -26,7 +26,7 @@
<q-btn
:label="showCompleted ? `Hide ${completedCount} Completed` : `Show ${completedCount} Completed`"
:icon="showCompleted ? 'visibility_off' : 'visibility'"
@click="showCompleted = !showCompleted"
@click="toggleShowCompleted"
dense
unelevated
no-caps
@ -39,7 +39,7 @@
dense
:table-class="{ 'table-bgcolor': !$q.dark.isActive, 'table-bgcolor-dark': $q.dark.isActive }"
class="remote-bg-tbl-sticky"
:data="filter"
:data="actions"
:columns="columns"
:visible-columns="visibleColumns"
:pagination.sync="pagination"
@ -69,7 +69,7 @@
</q-td>
<q-td>{{ props.row.due }}</q-td>
<q-td>{{ props.row.description }}</q-td>
<q-td v-if="props.row.action_type === 'chocoinstall'">
<q-td v-if="props.row.action_type === 'chocoinstall' && props.row.status === 'completed'">
<q-btn
color="primary"
icon="preview"
@ -107,6 +107,7 @@ export default {
selectedRow: null,
showCompleted: false,
selectedStatus: null,
completedCount: 0,
actionType: null,
hostname: "",
pagination: {
@ -145,14 +146,21 @@ export default {
html: true,
});
},
toggleShowCompleted() {
this.showCompleted = !this.showCompleted;
this.getPendingActions();
},
getPendingActions() {
let data = { showCompleted: this.showCompleted };
if (!!this.agentpk) data.agentPK = this.agentpk;
this.$q.loading.show();
this.clearRow();
this.$axios
.get(this.url)
.patch("/logs/pendingactions/", data)
.then(r => {
this.actions = Object.freeze(r.data);
if (!!this.agentpk) this.hostname = r.data[0].hostname;
this.completedCount = r.data.completed_count;
this.actions = Object.freeze(r.data.actions);
if (!!this.agentpk) this.hostname = r.data.actions[0].hostname;
this.$q.loading.hide();
})
.catch(e => {
@ -170,7 +178,7 @@ export default {
this.$q.loading.show();
const data = { pk: this.selectedRow };
this.$axios
.delete("/logs/cancelpendingaction/", { data: data })
.delete("/logs/pendingactions/", { data: data })
.then(r => {
this.$q.loading.hide();
this.getPendingActions();
@ -202,12 +210,6 @@ export default {
},
},
computed: {
url() {
return !!this.agentpk ? `/logs/${this.agentpk}/pendingactions/` : "/logs/allpendingactions/";
},
filter() {
return this.showCompleted ? this.actions : this.actions.filter(k => k.status === "pending");
},
columns() {
return !!this.agentpk ? this.agent_columns : this.all_columns;
},
@ -217,9 +219,6 @@ export default {
title() {
return !!this.agentpk ? `Pending Actions for ${this.hostname}` : "All Pending Actions";
},
completedCount() {
return this.actions.filter(k => k.status === "completed").length;
},
},
created() {
this.getPendingActions();